// 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; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.CommandLine; import com.google.devtools.build.lib.analysis.SourceManifestAction.ManifestType; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; import com.google.devtools.build.lib.analysis.actions.SymlinkTreeAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.RunUnder; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; import com.google.devtools.build.lib.syntax.Type; 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 java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * This class manages the creation of the runfiles symlink farms. * *
For executables that might depend on the existence of files at run-time, we create a symlink * farm: a directory which contains symlinks to the right locations for those runfiles. * *
The runfiles symlink farm serves two purposes. The first is to allow programs (and * programmers) to refer to files using their workspace-relative paths, regardless of whether the * files were source files or generated files, and regardless of which part of the package path they * came from. The second purpose is to ensure that all run-time dependencies are explicitly declared * in the BUILD files; programs may only use files which the build system knows that they depend on. * *
The symlink farm contains a MANIFEST file which describes its contents. The MANIFEST file * lists the names and contents of all of the symlinks in the symlink farm. For efficiency, Blaze's * dependency analysis ignores the actual symlinks and just looks at the MANIFEST file. It is an * invariant that the MANIFEST file should accurately represent the contents of the symlinks * whenever the MANIFEST file is present. build_runfile_links.py preserves this invariant (modulo * bugs - currently it has a bug where it may fail to preserve that invariant if it gets * interrupted). So the Blaze dependency analysis looks only at the MANIFEST file, rather than at * the individual symlinks. * *
We create an Artifact for the MANIFEST file and a RunfilesAction Action to create it. This * action does not depend on any other Artifacts. * *
When building an executable and running it, there are three things which must be built: the
* executable itself, the runfiles symlink farm (represented in the action graph by the Artifact for
* its MANIFEST), and the files pointed to by the symlinks in the symlink farm. To avoid redundancy
* in the dependency analysis, we create a Middleman Artifact which depends on all of these. Actions
* which will run an executable should depend on this Middleman Artifact.
*/
@Immutable
@AutoCodec
public final class RunfilesSupport {
private static final String RUNFILES_DIR_EXT = ".runfiles";
private final Runfiles runfiles;
private final Artifact runfilesInputManifest;
private final Artifact runfilesManifest;
private final Artifact runfilesMiddleman;
private final Artifact sourcesManifest;
private final Artifact owningExecutable;
private final boolean createSymlinks;
private final CommandLine args;
/**
* Creates the RunfilesSupport helper with the given executable and runfiles.
*
* @param ruleContext the rule context to create the runfiles support for
* @param executable the executable for whose runfiles this runfiles support is responsible, may
* be null
* @param runfiles the runfiles
*/
private static RunfilesSupport create(
RuleContext ruleContext, Artifact executable, Runfiles runfiles, CommandLine args) {
Artifact owningExecutable = Preconditions.checkNotNull(executable);
boolean createManifest = ruleContext.getConfiguration().buildRunfilesManifests();
boolean createSymlinks = createManifest && ruleContext.getConfiguration().buildRunfiles();
// Adding run_under target to the runfiles manifest so it would become part
// of runfiles tree and would be executable everywhere.
RunUnder runUnder = ruleContext.getConfiguration().getRunUnder();
if (runUnder != null && runUnder.getLabel() != null
&& TargetUtils.isTestRule(ruleContext.getRule())) {
TransitiveInfoCollection runUnderTarget =
ruleContext.getPrerequisite(":run_under", Mode.DONT_CHECK);
runfiles = new Runfiles.Builder(
ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles())
.merge(getRunfiles(runUnderTarget, ruleContext.getWorkspaceName()))
.merge(runfiles)
.build();
}
Preconditions.checkState(!runfiles.isEmpty());
Map The MANIFEST file represents the contents of all of the symlinks in the symlink farm. For
* efficiency, Blaze's dependency analysis ignores the actual symlinks and just looks at the
* MANIFEST file. It is an invariant that the MANIFEST file should accurately represent the
* contents of the symlinks whenever the MANIFEST file is present.
*/
@Nullable
public Artifact getRunfilesInputManifest() {
return runfilesInputManifest;
}
private static Artifact createRunfilesInputManifestArtifact(
RuleContext context, Artifact owningExecutable) {
// The executable may be null for emptyRunfiles
PathFragment relativePath = (owningExecutable != null)
? owningExecutable.getRootRelativePath()
: context.getPackageDirectory().getRelative(context.getLabel().getName());
String basename = relativePath.getBaseName();
PathFragment inputManifestPath = relativePath.replaceName(basename + ".runfiles_manifest");
return context.getDerivedArtifact(inputManifestPath,
context.getConfiguration().getBinDirectory(context.getRule().getRepository()));
}
/**
* Returns the MANIFEST file in the runfiles symlink farm if Bazel is run with
* --build_runfile_links. Returns the .runfiles_manifest file outside of the symlink farm, if
* Bazel is run with --nobuild_runfile_links. Returns null if --nobuild_runfile_manifests is
* passed.
*
* Beware: In most cases {@link #getRunfilesInputManifest} is the more appropriate function.
*/
@Nullable
public Artifact getRunfilesManifest() {
return runfilesManifest;
}
/** Returns the root directory of the runfiles symlink farm; otherwise, returns null. */
@Nullable
public Path getRunfilesDirectory() {
Artifact inputManifest = getRunfilesInputManifest();
if (inputManifest == null) {
return null;
}
return FileSystemUtils.replaceExtension(inputManifest.getPath(), RUNFILES_DIR_EXT);
}
/**
* Returns the files pointed to by the symlinks in the runfiles symlink farm. This method is slow.
*/
@VisibleForTesting
public Collection The "runfiles" action creates a symlink farm that links all the runfiles (which may come
* from different places, e.g. different package paths, generated files, etc.) into a single tree,
* so that programs can access them using the workspace-relative name.
*/
private static Artifact createRunfilesAction(
ActionConstructionContext context,
Runfiles runfiles,
boolean createSymlinks,
Artifact inputManifest) {
// Compute the names of the runfiles directory and its MANIFEST file.
context.getAnalysisEnvironment().registerAction(
SourceManifestAction.forRunfiles(
ManifestType.SOURCE_SYMLINKS, context.getActionOwner(), inputManifest, runfiles));
if (!createSymlinks) {
// Just return the manifest if that's all the build calls for.
return inputManifest;
}
PathFragment runfilesDir = FileSystemUtils.replaceExtension(inputManifest.getRootRelativePath(),
RUNFILES_DIR_EXT);
PathFragment outputManifestPath = runfilesDir.getRelative("MANIFEST");
BuildConfiguration config = context.getConfiguration();
Artifact outputManifest = context.getDerivedArtifact(
outputManifestPath, context.getBinDirectory());
context
.getAnalysisEnvironment()
.registerAction(
new SymlinkTreeAction(
context.getActionOwner(),
inputManifest,
outputManifest,
/*filesetTree=*/ false,
config.getActionEnvironment(),
config.runfilesEnabled()));
return outputManifest;
}
/**
* Creates an {@link Artifact} which writes the "sources only" manifest file.
*
* @param context the owner for the manifest action
* @param runfiles the runfiles
* @return the Artifact representing the file write action.
*/
private static Artifact createSourceManifest(
ActionConstructionContext context, Runfiles runfiles, Artifact owningExecutable) {
// Put the sources only manifest next to the MANIFEST file but call it SOURCES.
PathFragment executablePath = owningExecutable.getRootRelativePath();
PathFragment sourcesManifestPath = executablePath.getParentDirectory().getChild(
executablePath.getBaseName() + ".runfiles.SOURCES");
Artifact sourceOnlyManifest = context.getDerivedArtifact(
sourcesManifestPath,
context.getBinDirectory());
context.getAnalysisEnvironment().registerAction(SourceManifestAction.forRunfiles(
ManifestType.SOURCES_ONLY, context.getActionOwner(), sourceOnlyManifest, runfiles));
return sourceOnlyManifest;
}
/**
* Helper method that returns a collection of artifacts that are necessary for the runfiles of the
* given target. Note that the runfile symlink tree is never built, so this may include artifacts
* that end up not being used (see {@link Runfiles}).
*
* @return the Runfiles object
*/
private static Runfiles getRunfiles(TransitiveInfoCollection target, String workspaceName) {
RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class);
if (runfilesProvider != null) {
return runfilesProvider.getDefaultRunfiles();
} else {
return new Runfiles.Builder(workspaceName)
.addTransitiveArtifacts(target.getProvider(FilesToRunProvider.class).getFilesToRun())
.build();
}
}
/** Returns the unmodifiable list of expanded and tokenized 'args' attribute values. */
public CommandLine getArgs() {
return args;
}
/**
* Creates and returns a {@link RunfilesSupport} object for the given rule and executable. Note
* that this method calls back into the passed in rule to obtain the runfiles.
*/
public static RunfilesSupport withExecutable(
RuleContext ruleContext, Runfiles runfiles, Artifact executable) {
return RunfilesSupport.create(
ruleContext, executable, runfiles, computeArgs(ruleContext, CommandLine.EMPTY));
}
/**
* Creates and returns a {@link RunfilesSupport} object for the given rule and executable. Note
* that this method calls back into the passed in rule to obtain the runfiles.
*/
public static RunfilesSupport withExecutable(
RuleContext ruleContext, Runfiles runfiles, Artifact executable, List