blob: 5726071eba69f76ca896d6491887807bfaab5269 [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.analysis.config;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import javax.annotation.Nullable;
/**
* Logic for figuring out what base directories to place outputs generated from a given
* configuration.
*
* <p>In other words, when your output ends up in <code>blaze-out/x86-fastbuild/...</code>, this
* class is why.
*/
public class OutputDirectories {
/**
* Directories in the output tree.
*
* <p>The computation of the output directory should be a non-injective mapping from
* BuildConfigurationValue instances to strings. The result should identify the aspects of the
* configuration that should be reflected in the output file names. Furthermore the returned
* string must not contain shell metacharacters.
*
* <p>For configuration settings which are NOT part of the output directory name, rebuilding with
* a different value of such a setting will build in the same output directory. This means that
* any actions whose keys (see Action.getKey()) have changed will be rerun. That may result in a
* lot of recompilation.
*
* <p>For configuration settings which ARE part of the output directory name, rebuilding with a
* different value of such a setting will rebuild in a different output directory; this will
* result in higher disk usage and more work the <i>first</i> time you rebuild with a different
* setting, but will result in less work if you regularly switch back and forth between different
* settings.
*
* <p>With one important exception, it's sound to choose any subset of the config's components for
* this string, it just alters the dimensionality of the cache. In other words, it's a trade-off
* on the "injectiveness" scale: at one extreme (output directory name contains all data in the
* config, and is thus injective) you get extremely precise caching (no competition for the same
* output-file locations) but you have to rebuild for even the slightest change in configuration.
* At the other extreme (the output (directory name is a constant) you have very high competition
* for output-file locations, but if a slight change in configuration doesn't affect a particular
* build step, you're guaranteed not to have to rebuild it. The important exception has to do with
* multiple configurations: every configuration in the build must have a different output
* directory name so that their artifacts do not conflict.
*/
public enum OutputDirectory {
BIN("bin"),
GENFILES("genfiles"),
MIDDLEMAN("internal"),
TESTLOGS("testlogs"),
COVERAGE("coverage-metadata"),
BUILDINFO(BlazeDirectories.RELATIVE_BUILD_INFO_DIR),
OUTPUT("");
private final String name;
OutputDirectory(String name) {
// Must be a legal basename for root - multiple segments not allowed.
if (!name.isEmpty()) {
FileSystemUtils.checkBaseName(name);
}
this.name = name;
}
public ArtifactRoot getRoot(
String outputDirName, BlazeDirectories directories, String workspaceName) {
// e.g., execroot/my_workspace
Path execRoot = directories.getExecRoot(workspaceName);
// e.g., [[execroot/my_workspace]/bazel-out/config/bin]
return ArtifactRoot.asDerivedRoot(
execRoot,
this == MIDDLEMAN ? RootType.Middleman : RootType.Output,
directories.getRelativeOutputPath(),
outputDirName,
name);
}
}
private final BlazeDirectories directories;
private final String mnemonic;
private final ArtifactRoot outputDirectory;
private final ArtifactRoot binDirectory;
private final ArtifactRoot buildInfoDirectory;
private final ArtifactRoot genfilesDirectory;
private final ArtifactRoot coverageDirectory;
private final ArtifactRoot testlogsDirectory;
private final ArtifactRoot middlemanDirectory;
private final boolean mergeGenfilesDirectory;
private final boolean siblingRepositoryLayout;
private final Path execRoot;
OutputDirectories(
BlazeDirectories directories,
CoreOptions options,
@Nullable PlatformOptions platformOptions,
String mnemonic,
String workspaceName,
boolean siblingRepositoryLayout) {
this.directories = directories;
this.mnemonic = mnemonic;
this.outputDirectory = OutputDirectory.OUTPUT.getRoot(mnemonic, directories, workspaceName);
this.binDirectory = OutputDirectory.BIN.getRoot(mnemonic, directories, workspaceName);
this.buildInfoDirectory =
OutputDirectory.BUILDINFO.getRoot(mnemonic, directories, workspaceName);
this.genfilesDirectory = OutputDirectory.GENFILES.getRoot(mnemonic, directories, workspaceName);
this.coverageDirectory = OutputDirectory.COVERAGE.getRoot(mnemonic, directories, workspaceName);
this.testlogsDirectory = OutputDirectory.TESTLOGS.getRoot(mnemonic, directories, workspaceName);
this.middlemanDirectory =
OutputDirectory.MIDDLEMAN.getRoot(mnemonic, directories, workspaceName);
this.mergeGenfilesDirectory = options.mergeGenfilesDirectory;
this.siblingRepositoryLayout = siblingRepositoryLayout;
this.execRoot = directories.getExecRoot(workspaceName);
}
private ArtifactRoot buildDerivedRoot(
String nameFragment, RepositoryName repository, boolean isMiddleman) {
// e.g., execroot/mainRepoName/bazel-out/[repoName/]config/bin
// TODO(jungjw): Ideally, we would like to do execroot_base/repoName/bazel-out/config/bin
// instead. However, it requires individually symlinking the top-level elements of external
// repositories, which is blocked by a Windows symlink issue #8704.
RootType rootType;
if (repository.isMain()) {
rootType = isMiddleman ? RootType.SiblingMainMiddleman : RootType.SiblingMainOutput;
} else {
rootType = isMiddleman ? RootType.SiblingExternalMiddleman : RootType.SiblingExternalOutput;
}
return ArtifactRoot.asDerivedRoot(
execRoot,
rootType,
directories.getRelativeOutputPath(),
repository.getName(),
mnemonic,
nameFragment);
}
/** Returns the output directory for this build configuration. */
ArtifactRoot getOutputDirectory(RepositoryName repositoryName) {
return siblingRepositoryLayout ? buildDerivedRoot("", repositoryName, false) : outputDirectory;
}
/** Returns the bin directory for this build configuration. */
ArtifactRoot getBinDirectory(RepositoryName repositoryName) {
return siblingRepositoryLayout ? buildDerivedRoot("bin", repositoryName, false) : binDirectory;
}
/** Returns the build-info directory for this build configuration. */
ArtifactRoot getBuildInfoDirectory(RepositoryName repositoryName) {
return siblingRepositoryLayout
? buildDerivedRoot(BlazeDirectories.RELATIVE_BUILD_INFO_DIR, repositoryName, false)
: buildInfoDirectory;
}
/** Returns the genfiles directory for this build configuration. */
ArtifactRoot getGenfilesDirectory(RepositoryName repositoryName) {
return mergeGenfilesDirectory
? getBinDirectory(repositoryName)
: siblingRepositoryLayout
? buildDerivedRoot("genfiles", repositoryName, false)
: genfilesDirectory;
}
/**
* Returns the directory where coverage-related artifacts and metadata files should be stored.
* This includes for example uninstrumented class files needed for Jacoco's coverage reporting
* tools.
*/
ArtifactRoot getCoverageMetadataDirectory(RepositoryName repositoryName) {
return siblingRepositoryLayout
? buildDerivedRoot("coverage-metadata", repositoryName, false)
: coverageDirectory;
}
/** Returns the testlogs directory for this build configuration. */
ArtifactRoot getTestLogsDirectory(RepositoryName repositoryName) {
return siblingRepositoryLayout
? buildDerivedRoot("testlogs", repositoryName, false)
: testlogsDirectory;
}
/** Returns a relative path to the genfiles directory at execution time. */
PathFragment getGenfilesFragment(RepositoryName repositoryName) {
return getGenfilesDirectory(repositoryName).getExecPath();
}
/**
* Returns the path separator for the host platform. This is basically the same as {@link
* java.io.File#pathSeparator}, except that that returns the value for this JVM, which may or may
* not match the host platform. You should only use this when invoking tools that are known to use
* the native path separator, i.e., the path separator for the machine that they run on.
*/
String getHostPathSeparator() {
// TODO(bazel-team): Maybe do this in the constructor instead? This isn't serialization-safe.
return OS.getCurrent() == OS.WINDOWS ? ";" : ":";
}
/** Returns the internal directory (used for middlemen) for this build configuration. */
ArtifactRoot getMiddlemanDirectory(RepositoryName repositoryName) {
return siblingRepositoryLayout
? buildDerivedRoot("internal", repositoryName, true)
: middlemanDirectory;
}
String getMnemonic() {
return mnemonic;
}
String getOutputDirName() {
return getMnemonic();
}
boolean mergeGenfilesDirectory() {
return mergeGenfilesDirectory;
}
BlazeDirectories getDirectories() {
return directories;
}
}