| // 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); |
| } |
| } |