blob: 71890b24c7e448fd5ebc431876398f9a231c1238 [file] [log] [blame]
// Copyright 2017 The Bazel Authors. All rights reserved.
//
// 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 com.google.devtools.build.benchmark;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.shell.Command;
import com.google.devtools.build.lib.shell.CommandException;
import com.google.devtools.build.lib.shell.CommandResult;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Class that provides all needed feature of Bazel for benchmark. */
class BazelBuilder implements Builder {
private static final Logger logger = Logger.getLogger(BazelBuilder.class.getName());
private static final FileSystem fileSystem = new JavaIoFileSystem();
private static final String BAZEL_BINARY_PATH = "bazel-bin/src/bazel";
private static final Pattern ELAPSED_TIME_PATTERN = Pattern.compile("(?<=Elapsed time: )[0-9.]+");
private static final String DEFAULT_GIT_REPO = "https://github.com/bazelbuild/bazel.git";
private final Path generatedCodeDir;
private final Path builderDir;
private Path buildBinary = null;
private String currentCodeVersion = "";
BazelBuilder(Path generatedCodeDir, Path builderDir) {
this.generatedCodeDir = generatedCodeDir;
this.builderDir = builderDir;
}
@Override
public Path getBuildBinary(String codeVersion) throws IOException, CommandException {
if (buildBinary != null && currentCodeVersion.equals(codeVersion)) {
return buildBinary;
}
// git checkout codeVersion
String[] checkoutCommand = {"git", "checkout", codeVersion};
Command cmd = new Command(checkoutCommand, null, builderDir.toFile());
cmd.execute();
// bazel build src:bazel
String[] buildBazelCommand = {"bazel", "build", "src:bazel"};
cmd = new Command(buildBazelCommand, null, builderDir.toFile());
CommandResult result = cmd.execute();
// Get binary path, bazel output is in stderr
String output = new String(result.getStderr(), UTF_8).trim();
if (!output.contains(BAZEL_BINARY_PATH)) {
throw new IOException("Bazel binary " + BAZEL_BINARY_PATH + " is not in output of build.");
}
buildBinary = builderDir.resolve(BAZEL_BINARY_PATH);
currentCodeVersion = codeVersion;
return buildBinary;
}
@Override
public ImmutableList<String> getCommandFromConfig(
BuildTargetConfig targetConfig, BuildEnvConfig envConfig) {
return ImmutableList.<String>builder()
.add("build")
.add(targetConfig.getBuildTarget())
.addAll(envConfig.getBuildArgsList())
.build();
}
@Override
public double buildAndGetElapsedTime(Path buildBinary, ImmutableList<String> args)
throws CommandException {
List<String> cmdList = new ArrayList<>();
cmdList.add(buildBinary.toString());
cmdList.addAll(args);
String[] cmdArr = new String[cmdList.size()];
cmdArr = cmdList.toArray(cmdArr);
// Run build command
Command cmd = new Command(cmdArr, null, generatedCodeDir.toFile());
CommandResult result = cmd.execute();
// Get elapsed time from output
String output = new String(result.getStderr(), UTF_8).trim();
Matcher m = ELAPSED_TIME_PATTERN.matcher(output);
if (m.find()) {
try {
return (Double.parseDouble(m.group(0)));
} catch (NumberFormatException e) {
// Should not be here since we look for [0-9.]+
logger.log(Level.SEVERE, "Cannot parse " + m.group(0));
}
}
throw new CommandException(cmd, "Command didn't provide parsable output.");
}
@Override
public void clean() throws CommandException {
String[] cleanCommand = {"bazel", "clean", "--expunge"};
Command cmd = new Command(cleanCommand, null, generatedCodeDir.toFile());
cmd.execute();
}
@Override
public void prepare() throws IOException, CommandException {
prepareFromGitRepo(DEFAULT_GIT_REPO);
}
@Override
public ImmutableList<String> getCodeVersionsBetweenVersions(VersionFilter versionFilter)
throws CommandException {
return getListOfOutputFromCommand(
"git", "log",
versionFilter.getFrom() + ".." + versionFilter.getTo(), "--pretty=format:%H", "--reverse");
}
@Override
public ImmutableList<String> getCodeVersionsBetweenDates(DateFilter dateFilter)
throws CommandException {
return getListOfOutputFromCommand(
"git", "log",
"--after", dateFilter.getFromString(),
"--before", dateFilter.getToString(), "--pretty=format:%H", "--reverse");
}
@Override
public ImmutableList<String> getDatetimeForCodeVersions(ImmutableList<String> codeVersions)
throws CommandException {
return getListOfOutputFromCommandWithAdditionalParam(codeVersions,
"git", "show", "-s",
"--date=iso", "--pretty=format:%cd", "--date=format:%Y-%m-%d %H:%M:%S");
}
void prepareFromGitRepo(String gitRepo) throws IOException, CommandException {
// Try to pull git repo first, delete directory if failed.
if (builderDir.toFile().isDirectory()) {
try {
pullGitRepo();
} catch (CommandException e) {
FileSystemUtils.deleteTree(fileSystem.getPath(builderDir.toString()));
}
}
if (Files.notExists(builderDir)) {
try {
Files.createDirectories(builderDir);
} catch (IOException e) {
throw new IOException("Failed to create directory for bazel", e);
}
String[] gitCloneCommand = {"git", "clone", gitRepo, "."};
Command cmd = new Command(gitCloneCommand, null, builderDir.toFile());
cmd.execute();
}
// Assume the directory is what we need if not empty
}
private void pullGitRepo() throws CommandException {
String[] gitCloneCommand = {"git", "pull"};
Command cmd = new Command(gitCloneCommand, null, builderDir.toFile());
cmd.execute();
}
private ImmutableList<String> getListOfOutputFromCommand(String... command)
throws CommandException{
Command cmd = new Command(command, null, builderDir.toFile());
CommandResult result = cmd.execute();
String output = new String(result.getStdout(), UTF_8).trim();
return ImmutableList.copyOf(output.split("\n"));
}
private ImmutableList<String> getListOfOutputFromCommandWithAdditionalParam(
ImmutableList<String> additionalParam, String... command) throws CommandException{
ImmutableList<String> commandList =
ImmutableList.<String>builder().add(command).addAll(additionalParam).build();
String[] finalCommand = commandList.toArray(new String[0]);
return getListOfOutputFromCommand(finalCommand);
}
}