blob: 004335d388944f47d16305b52eec727181732e69 [file] [log] [blame]
// Copyright 2014 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.lib.runtime.commands;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.ProtoUtils;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.AllowedRuleClassInfo;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.AttributeDefinition;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.BuildLanguage;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.RuleDefinition;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.ProcessUtils;
import com.google.devtools.build.lib.util.StringUtilities;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.common.options.OptionsProvider;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Map;
/**
* An item that is returned by <code>blaze info</code>.
*/
public abstract class InfoItem {
protected final String name;
protected final String description;
protected final boolean hidden;
protected InfoItem(String name,
String description,
boolean hidden) {
this.name = name;
this.description = description;
this.hidden = hidden;
}
protected InfoItem(String name,
String description) {
this(name, description, false);
}
/**
* The name of the info key.
*/
public String getName() {
return name;
}
/**
* The help description of the info key.
*/
public String getDescription() {
return description;
}
/**
* Whether the key is printed when "blaze info" is invoked without arguments.
*
* <p>This is usually true for info keys that take multiple lines, thus, cannot really be
* included in the output of argumentless "blaze info".
*/
public boolean isHidden() {
return hidden;
}
/**
* Returns the value of the info key. The return value is directly printed to stdout.
*/
public abstract byte[] get(
Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException, InterruptedException;
protected static byte[] print(Object value) {
if (value instanceof byte[]) {
return (byte[]) value;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(
outputStream, StandardCharsets.UTF_8));
writer.print(value + "\n");
writer.flush();
return outputStream.toByteArray();
}
/**
* Info item for the workspace directory.
*/
public static final class WorkspaceInfoItem extends InfoItem {
public WorkspaceInfoItem() {
super("workspace",
"The working directory of the server.");
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(env.getRuntime().getWorkspace().getWorkspace());
}
}
/**
* Info item for the install_base directory.
*/
public static final class InstallBaseInfoItem extends InfoItem {
public InstallBaseInfoItem() {
super("install_base",
"The installation base directory.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(env.getRuntime().getWorkspace().getInstallBase());
}
}
/**
* Info item for the output_base directory.
*/
public static final class OutputBaseInfoItem extends InfoItem {
public OutputBaseInfoItem(String productName) {
super("output_base",
"A directory for shared " + productName
+ " state as well as tool and strategy specific subdirectories.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(env.getRuntime().getWorkspace().getOutputBase());
}
}
/**
* Info item for the execution_root directory.
*/
public static final class ExecutionRootInfoItem extends InfoItem {
public ExecutionRootInfoItem() {
super("execution_root",
"A directory that makes all input and output files visible to the build.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(env.getDirectories().getExecRoot(
configurationSupplier.get().getMainRepositoryName()));
}
}
/**
* Info item for the output_path directory.
*/
public static final class OutputPathInfoItem extends InfoItem {
public OutputPathInfoItem() {
super("output_path",
"Output directory",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(
env.getDirectories().getOutputPath(configurationSupplier.get().getMainRepositoryName()));
}
}
/**
* Info item for the {blaze,bazel}-bin directory.
*/
public static final class BlazeBinInfoItem extends InfoItem {
public BlazeBinInfoItem(String productName) {
super(productName + "-bin",
"Configuration dependent directory for binaries.",
false);
}
// This is one of the three (non-hidden) info items that require a configuration, because the
// corresponding paths contain the short name. Maybe we should recommend using the symlinks
// or make them hidden by default?
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(configurationSupplier);
return print(configurationSupplier.get().getBinDirectory(RepositoryName.MAIN).getRoot());
}
}
/**
* Info item for the {blaze,bazel}-genfiles directory.
*/
public static final class BlazeGenfilesInfoItem extends InfoItem {
public BlazeGenfilesInfoItem(String productName) {
super(productName + "-genfiles",
"Configuration dependent directory for generated files.",
false);
}
// This is one of the three (non-hidden) info items that require a configuration, because the
// corresponding paths contain the short name. Maybe we should recommend using the symlinks
// or make them hidden by default?
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(configurationSupplier);
return print(configurationSupplier.get().getGenfilesDirectory(RepositoryName.MAIN).getRoot());
}
}
/**
* Info item for the {blaze,bazel}-testlogs directory.
*/
public static final class BlazeTestlogsInfoItem extends InfoItem {
public BlazeTestlogsInfoItem(String productName) {
super(productName + "-testlogs",
"Configuration dependent directory for logs from a test run.",
false);
}
// This is one of the three (non-hidden) info items that require a configuration, because the
// corresponding paths contain the short name. Maybe we should recommend using the symlinks
// or make them hidden by default?
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(configurationSupplier);
return print(configurationSupplier.get().getTestLogsDirectory(RepositoryName.MAIN).getRoot());
}
}
/**
* Info item for release
*/
public static final class ReleaseInfoItem extends InfoItem {
public ReleaseInfoItem(String productName) {
super("release",
productName + " release identifier",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(BlazeVersionInfo.instance().getReleaseName());
}
}
/**
* Info item for server_pid
*/
public static final class ServerPidInfoItem extends InfoItem {
public ServerPidInfoItem(String productName) {
super("server_pid",
productName + " process id",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(ProcessUtils.getpid());
}
}
/**
* Info item for package_path
*/
public static final class PackagePathInfoItem extends InfoItem {
private final OptionsProvider commandOptions;
public PackagePathInfoItem(OptionsProvider commandOptions) {
super("package_path",
"The search path for resolving package labels.",
false);
this.commandOptions = commandOptions;
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(commandOptions);
PackageCacheOptions packageCacheOptions =
commandOptions.getOptions(PackageCacheOptions.class);
return print(Joiner.on(":").join(packageCacheOptions.packagePath));
}
}
private static MemoryUsage getMemoryUsage() {
MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
return memBean.getHeapMemoryUsage();
}
/**
* Info item for the used heap size
*/
public static final class UsedHeapSizeInfoItem extends InfoItem {
public UsedHeapSizeInfoItem() {
super("used-heap-size",
"The amount of used memory in bytes. Note that this is not a "
+ "good indicator of the actual memory use, as it includes any remaining inaccessible "
+ "memory.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(StringUtilities.prettyPrintBytes(getMemoryUsage().getUsed()));
}
}
/**
* Info item for the used heap size after garbage collection
*/
public static final class UsedHeapSizeAfterGcInfoItem extends InfoItem {
public UsedHeapSizeAfterGcInfoItem() {
super("used-heap-size-after-gc",
"The amount of used memory in bytes after a call to System.gc().",
true);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
System.gc();
return print(StringUtilities.prettyPrintBytes(getMemoryUsage().getUsed()));
}
}
/**
* Info item for the committed heap size
*/
public static final class CommitedHeapSizeInfoItem extends InfoItem {
public CommitedHeapSizeInfoItem() {
super("committed-heap-size",
"The amount of memory in bytes that is committed for the Java virtual machine to use",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(StringUtilities.prettyPrintBytes(getMemoryUsage().getCommitted()));
}
}
/**
* Info item for the max heap size
*/
public static final class MaxHeapSizeInfoItem extends InfoItem {
public MaxHeapSizeInfoItem() {
super("max-heap-size",
"The maximum amount of memory in bytes that can be used for memory management.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(StringUtilities.prettyPrintBytes(getMemoryUsage().getMax()));
}
}
/**
* Info item for the gc-count
*/
public static final class GcCountInfoItem extends InfoItem {
public GcCountInfoItem() {
super("gc-count",
"Number of garbage collection runs.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
// The documentation is not very clear on what it means to have more than
// one GC MXBean, so we just sum them up.
long gcCount = 0;
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
gcCount += gcBean.getCollectionCount();
}
return print(gcCount + "");
}
}
/** Info item for the name and version of the Java runtime environment. */
public static final class JavaRuntimeInfoItem extends InfoItem {
public JavaRuntimeInfoItem() {
super("java-runtime", "Name and version of the current Java runtime environment.", false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(
String.format(
"%s (build %s) by %s",
System.getProperty("java.runtime.name", "Unknown runtime"),
System.getProperty("java.runtime.version", "unknown"),
System.getProperty("java.vendor", "unknown")));
}
}
/** Info item for the name and version of the Java VM. */
public static final class JavaVirtualMachineInfoItem extends InfoItem {
public JavaVirtualMachineInfoItem() {
super("java-vm", "Name and version of the current Java virtual machine.", false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(
String.format(
"%s (build %s, %s) by %s",
System.getProperty("java.vm.name", "Unknown VM"),
System.getProperty("java.vm.version", "unknown"),
System.getProperty("java.vm.info", "unknown"),
System.getProperty("java.vm.vendor", "unknown")));
}
}
/** Info item for the location of the Java runtime. */
public static final class JavaHomeInfoItem extends InfoItem {
public JavaHomeInfoItem() {
super("java-home", "Location of the current Java runtime.", false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
String javaHome = System.getProperty("java.home");
if (javaHome == null) {
return print("unknown");
}
// Tunnel through a Path object in order to normalize the representation of the path.
Path javaHomePath = env.getRuntime().getFileSystem().getPath(javaHome);
return print(javaHomePath.getPathString());
}
}
/** Info item for the current character encoding settings. */
public static final class CharacterEncodingInfoItem extends InfoItem {
public CharacterEncodingInfoItem() {
super(
"character-encoding",
"Information about the character encoding used by the running JVM.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
return print(
String.format(
"file.encoding = %s, defaultCharset = %s",
System.getProperty("file.encoding", "unknown"), Charset.defaultCharset().name()));
}
}
/** Info item for the gc-time */
public static final class GcTimeInfoItem extends InfoItem {
public GcTimeInfoItem() {
super("gc-time",
"The approximate accumulated time spend on garbage collection.",
false);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
// The documentation is not very clear on what it means to have more than
// one GC MXBean, so we just sum them up.
long gcTime = 0;
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
gcTime += gcBean.getCollectionTime();
}
return print(gcTime + "ms");
}
}
/** Info item for the effective current client environment. */
public static final class ClientEnv extends InfoItem {
public ClientEnv() {
super(
"client-env",
"The specifications that need to be added to the project-specific rc file to freeze the"
+ " current client environment",
true);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
String result = "";
for (Map.Entry<String, String> entry : env.getWhitelistedActionEnv().entrySet()) {
// TODO(bazel-team): as the syntax of our rc-files does not support to express new-lines in
// values, we produce syntax errors if the value of the entry contains a newline character.
result += "build --action_env=" + entry.getKey() + "=" + entry.getValue() + "\n";
}
for (Map.Entry<String, String> entry : env.getWhitelistedTestEnv().entrySet()) {
// TODO(bazel-team): as the syntax of our rc-files does not support to express new-lines in
// values, we produce syntax errors if the value of the entry contains a newline character.
result += "build --test_env=" + entry.getKey() + "=" + entry.getValue() + "\n";
}
return print(result);
}
}
/**
* Info item for the default package. It is deprecated, it still works, when
* explicitly requested, but are not shown by default. It prints multi-line messages and thus
* don't play well with grep. We don't print them unless explicitly requested.
* @deprecated
*/
@Deprecated
public static final class DefaultsPackageInfoItem extends InfoItem {
public DefaultsPackageInfoItem() {
super("defaults-package",
"Default packages used as implicit dependencies",
true);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(env.getRuntime().getDefaultsPackageContent());
}
}
private static AllowedRuleClassInfo getAllowedRuleClasses(
Collection<RuleClass> ruleClasses, Attribute attr) {
AllowedRuleClassInfo.Builder info = AllowedRuleClassInfo.newBuilder();
info.setPolicy(AllowedRuleClassInfo.AllowedRuleClasses.ANY);
if (attr.isStrictLabelCheckingEnabled()
&& attr.getAllowedRuleClassesPredicate() != Predicates.<RuleClass>alwaysTrue()) {
info.setPolicy(AllowedRuleClassInfo.AllowedRuleClasses.SPECIFIED);
Predicate<RuleClass> filter = attr.getAllowedRuleClassesPredicate();
for (RuleClass otherClass : Iterables.filter(ruleClasses, filter)) {
if (otherClass.isDocumented()) {
info.addAllowedRuleClass(otherClass.getName());
}
}
}
return info.build();
}
/**
* Returns a byte array containing a proto-buffer describing the build language.
*/
private static byte[] getBuildLanguageDefinition(RuleClassProvider provider) {
BuildLanguage.Builder resultPb = BuildLanguage.newBuilder();
Collection<RuleClass> ruleClasses = provider.getRuleClassMap().values();
for (RuleClass ruleClass : ruleClasses) {
if (!ruleClass.isDocumented()) {
continue;
}
RuleDefinition.Builder rulePb = RuleDefinition.newBuilder();
rulePb.setName(ruleClass.getName());
for (Attribute attr : ruleClass.getAttributes()) {
if (!attr.isDocumented()) {
continue;
}
AttributeDefinition.Builder attrPb = AttributeDefinition.newBuilder();
attrPb.setName(attr.getName());
// The protocol compiler, in its infinite wisdom, generates the field as one of the
// integer type and the getTypeEnum() method is missing. WTF?
attrPb.setType(ProtoUtils.getDiscriminatorFromType(attr.getType()));
attrPb.setMandatory(attr.isMandatory());
if (BuildType.isLabelType(attr.getType())) {
attrPb.setAllowedRuleClasses(getAllowedRuleClasses(ruleClasses, attr));
}
rulePb.addAttribute(attrPb);
}
resultPb.addRule(rulePb);
}
return resultPb.build().toByteArray();
}
/**
* Info item for the build language. It is deprecated, it still works, when
* explicitly requested, but are not shown by default. It prints multi-line messages and thus
* don't play well with grep. We don't print them unless explicitly requested.
* @Deprecated
*/
@Deprecated
public static final class BuildLanguageInfoItem extends InfoItem {
public BuildLanguageInfoItem() {
super("build-language",
"A protobuffer with the build language structure",
true);
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(env);
return print(getBuildLanguageDefinition(env.getRuntime().getRuleClassProvider()));
}
}
/**
* Info item for the default package path. It is deprecated, it still works, when
* explicitly requested, but are not shown by default. It prints multi-line messages and thus
* don't play well with grep. We don't print them unless explicitly requested.
* @deprecated
*/
@Deprecated
public static final class DefaultPackagePathInfoItem extends InfoItem {
private final OptionsProvider commandOptions;
public DefaultPackagePathInfoItem(OptionsProvider commandOptions) {
super("default-package-path",
"The default package path",
true);
this.commandOptions = commandOptions;
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env)
throws AbruptExitException {
checkNotNull(commandOptions);
return print(Joiner.on(":").join(
commandOptions.getOptions(PackageCacheOptions.class).packagePath));
}
}
/**
* Info item for the make environment.
*/
public static class MakeInfoItem extends InfoItem {
public MakeInfoItem(String name, String description) {
super(name, description, false);
}
@Override
public String getDescription() {
return "Make environment variable '" + name + "'";
}
@Override
public byte[] get(Supplier<BuildConfiguration> configurationSupplier, CommandEnvironment env) {
return print(description);
}
}
}