Skip to content

Commit d4c772c

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 d4c772c

File tree

8 files changed

+534
-66
lines changed

8 files changed

+534
-66
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

+141-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/BuildMutatorConfigurator.java

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

77
public interface BuildMutatorConfigurator {
8+
9+
/**
10+
* This should only be called when {@see #enabled} returns true
11+
*/
812
BuildMutator configure(Config scenario, String scenarioName, InvocationSettings settings, String key);
13+
14+
/**
15+
* Returns true if this BuildMutator is enabled for targeted scenario. This can filter
16+
* on specific build types.
17+
*/
18+
default boolean enabled(Config scenario, String scenarioName, InvocationSettings settings, String key) {
19+
return scenario.hasPath(key);
20+
}
921
}
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,117 @@
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+
Config scenario = getBuildConfig(rootScenario, settings, null);
77+
final List<BuildMutator> mutators = new ArrayList<>();
78+
final List<? extends Config> list = scenario.getConfigList(key);
79+
for (Config config : list) {
80+
final ExecuteCommandSchedule schedule = ConfigUtil
81+
.enumValue(config, "schedule", ExecuteCommandSchedule.class, null);
82+
if (schedule == null) {
83+
throw new IllegalArgumentException(
84+
"Schedule for executing commands is not specified");
85+
}
86+
List<String> commands = ConfigUtil.strings(config, "commands");
87+
if (commands.isEmpty()) {
88+
throw new IllegalArgumentException(
89+
String.format(
90+
"No commands specified for 'execute-command-before' in scenario %s",
91+
scenarioName)
92+
);
93+
}
94+
mutators.add(
95+
newInstance(scenario, scenarioName, settings, key, commandInvoker, schedule,
96+
commands));
97+
}
98+
return new CompositeBuildMutator(mutators);
99+
}
100+
101+
public boolean enabled(Config rootScenario, String scenarioName, InvocationSettings settings, String key) {
102+
Config scenario = getBuildConfig(rootScenario, settings, null);
103+
return scenario != null && scenario.hasPath(key) && !scenario.getConfigList(key)
104+
.isEmpty();
105+
}
106+
}
107+
108+
@Override
109+
public String toString() {
110+
return getClass().getSimpleName() + "(" + schedule + ")";
111+
}
112+
113+
public enum ExecuteCommandSchedule {
114+
SCENARIO, BUILD
115+
}
116+
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.gradle.profiler.mutations;
2+
3+
import java.io.IOException;
4+
import java.util.List;
5+
6+
public class ProcessBuilderCommandInvoker implements CommandInvoker {
7+
8+
@Override
9+
public int execute(List<String> command) {
10+
try {
11+
if (command == null || command.isEmpty()) {
12+
throw new IllegalArgumentException(
13+
String.format("command cannot be null or empty, was %s", command));
14+
}
15+
ProcessBuilder processBuilder = new ProcessBuilder(command)
16+
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
17+
.redirectError(ProcessBuilder.Redirect.INHERIT);
18+
Process process = processBuilder.start();
19+
return process.waitFor();
20+
} catch (IOException | InterruptedException e) {
21+
throw new RuntimeException(e);
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)