// 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 symlinks = runfiles.asMapWithoutRootSymlinks(); if (executable != null && !symlinks.containsValue(executable)) { throw new IllegalStateException("main program " + executable + " not included in runfiles"); } Artifact runfilesInputManifest; Artifact runfilesManifest; if (createManifest) { runfilesInputManifest = createRunfilesInputManifestArtifact(ruleContext, owningExecutable); runfilesManifest = createRunfilesAction(ruleContext, runfiles, createSymlinks, runfilesInputManifest); } else { runfilesInputManifest = null; runfilesManifest = null; } Artifact runfilesMiddleman = createRunfilesMiddleman(ruleContext, owningExecutable, runfiles, runfilesManifest); Artifact sourcesManifest = createSourceManifest(ruleContext, runfiles, owningExecutable); return new RunfilesSupport( runfiles, runfilesInputManifest, runfilesManifest, runfilesMiddleman, sourcesManifest, owningExecutable, createSymlinks, args); } @AutoCodec.Instantiator @VisibleForSerialization RunfilesSupport( Runfiles runfiles, Artifact runfilesInputManifest, Artifact runfilesManifest, Artifact runfilesMiddleman, Artifact sourcesManifest, Artifact owningExecutable, boolean createSymlinks, CommandLine args) { this.runfiles = runfiles; this.runfilesInputManifest = runfilesInputManifest; this.runfilesManifest = runfilesManifest; this.runfilesMiddleman = runfilesMiddleman; this.sourcesManifest = sourcesManifest; this.owningExecutable = owningExecutable; this.createSymlinks = createSymlinks; this.args = args; } /** * Returns the executable owning this RunfilesSupport. Only use from Skylark. */ public Artifact getExecutable() { return owningExecutable; } /** * Returns the exec path of the directory where the runfiles contained in this * RunfilesSupport are generated. When the owning rule has no executable, * returns null. */ public PathFragment getRunfilesDirectoryExecPath() { PathFragment executablePath = owningExecutable.getExecPath(); return executablePath.getParentDirectory().getChild( executablePath.getBaseName() + RUNFILES_DIR_EXT); } /** @return whether or not runfiles symlinks should be created */ public boolean getCreateSymlinks() { return createSymlinks; } public Runfiles getRunfiles() { return runfiles; } /** * Returns the .runfiles_manifest file outside of the runfiles symlink farm. Returns null if * --nobuild_runfile_manifests is in effect. * *

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 getRunfilesSymlinkTargets() { return getRunfilesSymlinks().values(); } /** * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This * method is slow. */ // We should make this VisibleForTesting, but it is still used by TestHelper public Set getRunfilesSymlinkNames() { return getRunfilesSymlinks().keySet(); } /** * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This * method is slow. */ @VisibleForTesting public Map getRunfilesSymlinks() { return runfiles.asMapWithoutRootSymlinks(); } /** * Returns both runfiles artifacts and "conditional" artifacts that may be part of a Runfiles * PruningManifest. This means the returned set may be an overapproximation of the actual set of * runfiles (see {@link Runfiles.PruningManifest}). */ public Iterable getRunfilesArtifacts() { return runfiles.getArtifacts(); } /** * Returns the name of the workspace that the build is occurring in. */ public PathFragment getWorkspaceName() { return runfiles.getSuffix(); } /** * Returns the middleman artifact that depends on getExecutable(), * getRunfilesManifest(), and getRunfilesSymlinkTargets(). Anything which * needs to actually run the executable should depend on this. */ public Artifact getRunfilesMiddleman() { return runfilesMiddleman; } /** * Returns the Sources manifest. */ public Artifact getSourceManifest() { return sourcesManifest; } private static Artifact createRunfilesMiddleman( ActionConstructionContext context, Artifact owningExecutable, Runfiles runfiles, @Nullable Artifact runfilesManifest) { NestedSetBuilder deps = NestedSetBuilder.stableOrder(); deps.addTransitive(runfiles.getAllArtifacts()); if (runfilesManifest != null) { deps.add(runfilesManifest); } else { deps.addAll(SourceManifestAction.getDependencies(runfiles)); } return context .getAnalysisEnvironment() .getMiddlemanFactory() .createRunfilesMiddleman( context.getActionOwner(), owningExecutable, deps.build(), context.getMiddlemanDirectory(), "runfiles"); } /** * Creates a runfiles action for all of the specified files, and returns the output artifact (the * artifact for the MANIFEST file). * *

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 appendingArgs) { return RunfilesSupport.create( ruleContext, executable, runfiles, computeArgs(ruleContext, CommandLine.of(appendingArgs))); } /** * Creates and returns a {@link RunfilesSupport} object for the given rule, executable, runfiles * and args. */ public static RunfilesSupport withExecutable( RuleContext ruleContext, Runfiles runfiles, Artifact executable, CommandLine appendingArgs) { return RunfilesSupport.create( ruleContext, executable, runfiles, computeArgs(ruleContext, appendingArgs)); } private static CommandLine computeArgs( RuleContext ruleContext, CommandLine additionalArgs) { if (!ruleContext.getRule().isAttrDefined("args", Type.STRING_LIST)) { // Some non-_binary rules create RunfilesSupport instances; it is fine to not have an args // attribute here. return additionalArgs; } return CommandLine.concat( ruleContext.getExpander().withDataLocations().tokenized("args"), additionalArgs); } }