Skip to content

Commit 3d31a95

Browse files
spabeggsAlexBeggs
authored andcommitted
Add execute-command-before BuildMutator
- 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]>
1 parent 394d202 commit 3d31a95

14 files changed

+590
-90
lines changed

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ A scenario can define changes that should be applied to the source before each b
270270
- `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`).
271271
- `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`).
272272
- `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`).
273+
- `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.
273274
- `git-checkout`: Checks out a specific commit for the build step, and a different one for the cleanup step.
274275
- `git-revert`: Reverts a given set of commits before the build and resets it afterward.
275276
- `iterations`: Number of builds to actually measure
@@ -325,11 +326,43 @@ You can compare Gradle against Bazel, Buck, and Maven by specifying their equiva
325326

326327
build_some_target {
327328
tasks = ["assemble"]
329+
execute-command-before = [
330+
# Gradle specific executions
331+
# Execute a command in the bash shell
332+
{
333+
schedule = BUILD
334+
commands = ["/bin/bash","-c","echo helloworld"]
335+
},
336+
}
328337

329338
bazel {
330339
# If empty, it will be infered from BAZEL_HOME environment variable
331340
home = "/path/to/bazel/home"
332341
targets = ["build" "//some/target"]
342+
execute-command-before = [
343+
# Bazel specific executions
344+
# execute a command prior to the scenario running.
345+
{
346+
schedule = SCENARIO
347+
# Note: Ensure that this is calling the same Bazel that is used in the benchmarks
348+
commands = ["bazel","clean","--expunge"]
349+
},
350+
# Execute a command in the bash shell
351+
{
352+
schedule = BUILD
353+
commands = ["/bin/bash","-c","echo helloworld"]
354+
},
355+
# Remove the contents of the remote cache Bazel bucket
356+
{
357+
schedule = BUILD
358+
commands = ["gsutil","-o","Credentials:gs_service_key_file=bazel-benchmark-bucket.json","-m","rm","gs://bazel-benchmark-bucket/**"]
359+
},
360+
# Display the total size of the bucket
361+
{
362+
schedule = BUILD
363+
commands = ["gsutil","-o","Credentials:gs_service_key_file=bazel-benchmark-bucket.json","du","-sh","gs://bazel-benchmark-bucket"]
364+
},
365+
]
333366
}
334367
}
335368

@@ -344,6 +377,14 @@ You can compare Gradle against Bazel, Buck, and Maven by specifying their equiva
344377
# If empty, it will be infered from BUCK_HOME environment variable
345378
home = "/path/to/buck/home"
346379
type = "android_binary" // can be a Buck build rule type or "all"
380+
execute-command-before = [
381+
# Buck specific executions
382+
# Execute a command in the bash shell
383+
{
384+
schedule = BUILD
385+
commands = ["/bin/bash","-c","echo helloworld"]
386+
},
387+
}
347388
}
348389
}
349390
build_resources {

src/main/java/org/gradle/profiler/ScenarioLoader.java

+140-66
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.gradle.profiler;
2+
3+
import static org.gradle.profiler.ScenarioLoader.BAZEL;
4+
import static org.gradle.profiler.ScenarioLoader.BUCK;
5+
import static org.gradle.profiler.ScenarioLoader.MAVEN;
6+
7+
import com.typesafe.config.Config;
8+
import javax.annotation.Nullable;
9+
10+
public class ScenarioUtil {
11+
12+
/**
13+
* Returns the specific config for type of build that is running,
14+
* if the scenario doesn't define the build, then this default value is returned
15+
*
16+
* @param rootScenario the root scenario config
17+
* @param settings the invocation settings that indicates the type of build selected
18+
* @param defaultScenario if the scenario doesn't define the build, then this default value is returned, this can be null.
19+
* @return if the build config exists or if the scenario doesn't define the build, then this default value is returned
20+
*/
21+
public static Config getBuildConfig(Config rootScenario, InvocationSettings settings, @Nullable
22+
Config defaultScenario) {
23+
Config scenario;
24+
if (settings.isBazel()) {
25+
scenario = getConfigOrDefault(rootScenario, BAZEL, defaultScenario);
26+
} else if (settings.isBuck()) {
27+
scenario = getConfigOrDefault(rootScenario, BUCK, defaultScenario);
28+
} else if (settings.isMaven()) {
29+
scenario = getConfigOrDefault(rootScenario, MAVEN, defaultScenario);
30+
} else {
31+
scenario = rootScenario;
32+
}
33+
return scenario;
34+
}
35+
36+
private static Config getConfigOrDefault(Config rootScenario, String key, @Nullable Config defaultScenario) {
37+
Config scenario;
38+
if (rootScenario.hasPath(key)) {
39+
scenario = rootScenario.getConfig(key);
40+
} else {
41+
scenario = defaultScenario;
42+
} return scenario;
43+
}
44+
}

src/main/java/org/gradle/profiler/mutations/AbstractBuildMutatorWithoutOptionsConfigurator.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ public abstract class AbstractBuildMutatorWithoutOptionsConfigurator implements
1010

1111
@Override
1212
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
13-
boolean enabled = scenario.getBoolean(key);
14-
return enabled ? createBuildMutator(settings) : BuildMutator.NOOP;
13+
return new HasPathBuildMutatorConfigurator(() -> {
14+
boolean enabled = scenario.getBoolean(key);
15+
return enabled ? createBuildMutator(settings) : BuildMutator.NOOP;
16+
}).configure(scenario,scenarioName,settings,key);
1517
}
1618
}

src/main/java/org/gradle/profiler/mutations/AbstractCleanupMutator.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,14 @@ protected static void delete(File f) {
6666
protected static abstract class Configurator implements BuildMutatorConfigurator {
6767
@Override
6868
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
69-
CleanupSchedule schedule = ConfigUtil.enumValue(scenario, key, CleanupSchedule.class, null);
70-
if (schedule == null) {
71-
throw new IllegalArgumentException("Schedule for cleanup is not specified");
72-
}
73-
return newInstance(scenario, scenarioName, settings, key, schedule);
69+
return new HasPathBuildMutatorConfigurator(() -> {
70+
CleanupSchedule schedule = ConfigUtil
71+
.enumValue(scenario, key, CleanupSchedule.class, null);
72+
if (schedule == null) {
73+
throw new IllegalArgumentException("Schedule for cleanup is not specified");
74+
}
75+
return newInstance(scenario, scenarioName, settings, key, schedule);
76+
}).configure(scenario, scenarioName, settings, key);
7477
}
7578

7679
protected abstract BuildMutator newInstance(Config scenario, String scenarioName, InvocationSettings settings, String key, CleanupSchedule schedule);

src/main/java/org/gradle/profiler/mutations/BuildMutatorConfigurator.java

+1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
import org.gradle.profiler.InvocationSettings;
66

77
public interface BuildMutatorConfigurator {
8+
89
BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key);
910
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.gradle.profiler.mutations;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Interface which provides a means to execute a command
7+
*/
8+
public interface CommandInvoker {
9+
10+
/**
11+
* @param command the command to execute
12+
* @return the exit code of the result of executing the command
13+
*/
14+
int execute(List<String> command);
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package org.gradle.profiler.mutations;
2+
3+
import static org.gradle.profiler.ScenarioUtil.getBuildConfig;
4+
5+
import com.google.common.annotations.VisibleForTesting;
6+
import com.typesafe.config.Config;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import org.gradle.profiler.BuildContext;
10+
import org.gradle.profiler.BuildMutator;
11+
import org.gradle.profiler.CompositeBuildMutator;
12+
import org.gradle.profiler.ConfigUtil;
13+
import org.gradle.profiler.InvocationSettings;
14+
import org.gradle.profiler.ScenarioContext;
15+
16+
public class ExecuteCommandBuildMutator implements BuildMutator {
17+
18+
private ExecuteCommandSchedule schedule;
19+
private List<String> commands;
20+
private CommandInvoker commandInvoker;
21+
22+
public ExecuteCommandBuildMutator(ExecuteCommandSchedule schedule,
23+
List<String> commands, CommandInvoker commandInvoker) {
24+
this.schedule = schedule;
25+
this.commands = commands;
26+
this.commandInvoker = commandInvoker;
27+
}
28+
29+
@Override
30+
public void beforeBuild(BuildContext context) {
31+
if (schedule == ExecuteCommandSchedule.BUILD) {
32+
execute();
33+
}
34+
}
35+
36+
@Override
37+
public void beforeScenario(ScenarioContext context) {
38+
if (schedule == ExecuteCommandSchedule.SCENARIO) {
39+
execute();
40+
}
41+
}
42+
43+
protected void execute() {
44+
String commandStr = String.join(" ", commands);
45+
System.out.println(String.format("> Executing command `%s`", commandStr));
46+
int result = commandInvoker.execute(commands);
47+
if (result != 0) {
48+
System.err.println(
49+
String.format("Unexpected exit code %s for command `%s`", result, commandStr)
50+
);
51+
}
52+
}
53+
54+
public static class Configurator implements BuildMutatorConfigurator {
55+
56+
private CommandInvoker commandInvoker;
57+
58+
public Configurator() {
59+
this(new ProcessBuilderCommandInvoker());
60+
}
61+
62+
@VisibleForTesting
63+
Configurator(CommandInvoker commandInvoker) {
64+
this.commandInvoker = commandInvoker;
65+
}
66+
67+
private BuildMutator newInstance(Config scenario, String scenarioName,
68+
InvocationSettings settings, String key,
69+
CommandInvoker commandInvoker, ExecuteCommandSchedule schedule, List<String> commands) {
70+
return new ExecuteCommandBuildMutator(schedule, commands, commandInvoker);
71+
}
72+
73+
@Override
74+
public BuildMutator configure(Config rootScenario, String scenarioName,
75+
InvocationSettings settings, String key) {
76+
if (enabled(rootScenario, scenarioName, settings, key)) {
77+
Config scenario = getBuildConfig(rootScenario, settings, null);
78+
final List<BuildMutator> mutators = new ArrayList<>();
79+
final List<? extends Config> list = scenario.getConfigList(key);
80+
for (Config config : list) {
81+
final ExecuteCommandSchedule schedule = ConfigUtil
82+
.enumValue(config, "schedule", ExecuteCommandSchedule.class, null);
83+
if (schedule == null) {
84+
throw new IllegalArgumentException(
85+
"Schedule for executing commands is not specified");
86+
}
87+
List<String> commands = ConfigUtil.strings(config, "commands");
88+
if (commands.isEmpty()) {
89+
throw new IllegalArgumentException(
90+
String.format(
91+
"No commands specified for 'execute-command-before' in scenario %s",
92+
scenarioName)
93+
);
94+
}
95+
mutators.add(
96+
newInstance(scenario, scenarioName, settings, key, commandInvoker, schedule,
97+
commands));
98+
}
99+
return new CompositeBuildMutator(mutators);
100+
} else {
101+
return BuildMutator.NOOP;
102+
}
103+
}
104+
105+
private boolean enabled(Config rootScenario, String scenarioName, InvocationSettings settings, String key) {
106+
Config scenario = getBuildConfig(rootScenario, settings, null);
107+
return scenario != null && scenario.hasPath(key) && !scenario.getConfigList(key)
108+
.isEmpty();
109+
}
110+
}
111+
112+
@Override
113+
public String toString() {
114+
return getClass().getSimpleName() + "(" + schedule + ")";
115+
}
116+
117+
public enum ExecuteCommandSchedule {
118+
SCENARIO, BUILD
119+
}
120+
121+
}

src/main/java/org/gradle/profiler/mutations/FileChangeMutatorConfigurator.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ public FileChangeMutatorConfigurator(Class<? extends AbstractFileChangeMutator>
2222

2323
@Override
2424
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
25-
List<BuildMutator> mutatorsForKey = new ArrayList<>();
26-
for (File sourceFileToChange : sourceFiles(scenario, scenarioName, settings.getProjectDir(), key)) {
27-
mutatorsForKey.add(getBuildMutatorForFile(sourceFileToChange));
28-
}
25+
return new HasPathBuildMutatorConfigurator(() -> {
26+
List<BuildMutator> mutatorsForKey = new ArrayList<>();
27+
for (File sourceFileToChange : sourceFiles(scenario, scenarioName,
28+
settings.getProjectDir(), key)) {
29+
mutatorsForKey.add(getBuildMutatorForFile(sourceFileToChange));
30+
}
2931

30-
return new CompositeBuildMutator(mutatorsForKey);
32+
return new CompositeBuildMutator(mutatorsForKey);
33+
}).configure(scenario, scenarioName, settings, key);
3134
}
3235

3336
private BuildMutator getBuildMutatorForFile(File sourceFileToChange) {

src/main/java/org/gradle/profiler/mutations/GitCheckoutMutator.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,16 @@ private void checkout(String target) {
6363
public static class Configurator implements BuildMutatorConfigurator {
6464
@Override
6565
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
66-
Config config = scenario.getConfig(key);
67-
String cleanup = ConfigUtil.string(config, "cleanup", null);
68-
String build = ConfigUtil.string(config, "build", null);
69-
if (build == null) {
70-
throw new IllegalArgumentException("No git-checkout target specified for build");
71-
}
72-
return new GitCheckoutMutator(settings.getProjectDir(), cleanup, build);
66+
return new HasPathBuildMutatorConfigurator(() -> {
67+
Config config = scenario.getConfig(key);
68+
String cleanup = ConfigUtil.string(config, "cleanup", null);
69+
String build = ConfigUtil.string(config, "build", null);
70+
if (build == null) {
71+
throw new IllegalArgumentException(
72+
"No git-checkout target specified for build");
73+
}
74+
return new GitCheckoutMutator(settings.getProjectDir(), cleanup, build);
75+
}).configure(scenario, scenarioName, settings, key);
7376
}
7477
}
7578

src/main/java/org/gradle/profiler/mutations/GitRevertMutator.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ private void abortRevert() {
6060
public static class Configurator implements BuildMutatorConfigurator {
6161
@Override
6262
public BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key) {
63-
List<String> commits = ConfigUtil.strings(scenario, key);
64-
if (commits.isEmpty()) {
65-
throw new IllegalArgumentException("No commits specified for git-revert");
66-
}
67-
return new GitRevertMutator(settings.getProjectDir(), commits);
63+
return new HasPathBuildMutatorConfigurator(() -> {
64+
List<String> commits = ConfigUtil.strings(scenario, key);
65+
if (commits.isEmpty()) {
66+
throw new IllegalArgumentException("No commits specified for git-revert");
67+
}
68+
return new GitRevertMutator(settings.getProjectDir(), commits);
69+
}).configure(scenario, scenarioName, settings, key);
6870
}
6971
}
7072

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.gradle.profiler.mutations;
2+
3+
import com.typesafe.config.Config;
4+
import java.util.function.Supplier;
5+
import org.gradle.profiler.BuildMutator;
6+
import org.gradle.profiler.InvocationSettings;
7+
8+
class HasPathBuildMutatorConfigurator implements BuildMutatorConfigurator {
9+
10+
private Supplier<BuildMutator> configurator;
11+
12+
HasPathBuildMutatorConfigurator(Supplier<BuildMutator> configurator) {
13+
this.configurator = configurator;
14+
}
15+
16+
@Override
17+
public BuildMutator configure(Config scenario, String scenarioName,
18+
InvocationSettings settings, String key) {
19+
BuildMutator buildMutator;
20+
if (scenario.hasPath(key)) {
21+
buildMutator = configurator.get();
22+
} else {
23+
buildMutator = BuildMutator.NOOP;
24+
}
25+
return buildMutator;
26+
}
27+
}

0 commit comments

Comments
 (0)