blob: f3b1ec872f3d11faeff7034e65ed1de662636d90 [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.rules.cpp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.AbstractAction;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionExecutionException;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.ActionRegistry;
import com.google.devtools.build.lib.actions.ActionResult;
import com.google.devtools.build.lib.actions.Actions;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.SymlinkAction;
import com.google.devtools.build.lib.server.FailureDetails.SymlinkAction.Code;
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.util.DetailedExitCode;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import javax.annotation.Nullable;
/**
* Creates mangled symlinks in the solib directory for all shared libraries. Libraries that have a
* potential to contain SONAME field rely on the mangled symlink to the parent directory instead.
*
* <p>Such symlinks are used by the linker to ensure that all rpath entries can be specified
* relative to the $ORIGIN.
*/
@AutoCodec
@Immutable
public final class SolibSymlinkAction extends AbstractAction {
private final Artifact symlink;
@VisibleForSerialization
SolibSymlinkAction(
ActionOwner owner, Artifact primaryInput, Artifact primaryOutput) {
super(
owner,
NestedSetBuilder.create(Order.STABLE_ORDER, primaryInput),
ImmutableSet.of(primaryOutput));
Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(primaryInput.getFilename()));
this.symlink = Preconditions.checkNotNull(primaryOutput);
}
@Override
public ActionResult execute(ActionExecutionContext actionExecutionContext)
throws ActionExecutionException {
Path mangledPath = actionExecutionContext.getInputPath(symlink);
try {
mangledPath.createSymbolicLink(actionExecutionContext.getInputPath(getPrimaryInput()));
} catch (IOException e) {
String message =
String.format(
"failed to create _solib symbolic link '%s' to target '%s': %s",
symlink.prettyPrint(), getPrimaryInput(), e.getMessage());
DetailedExitCode code =
DetailedExitCode.of(
FailureDetail.newBuilder()
.setMessage(message)
.setSymlinkAction(
SymlinkAction.newBuilder().setCode(Code.LINK_CREATION_IO_EXCEPTION))
.build());
throw new ActionExecutionException(message, e, this, false, code);
}
return ActionResult.EMPTY;
}
@Override
protected void computeKey(
ActionKeyContext actionKeyContext,
@Nullable Artifact.ArtifactExpander artifactExpander,
Fingerprint fp) {
fp.addPath(symlink.getExecPath());
fp.addPath(getPrimaryInput().getExecPath());
}
@Override
public String getMnemonic() {
return "SolibSymlink";
}
@Override
protected String getRawProgressMessage() {
return null;
}
/**
* Replaces shared library artifact with mangled symlink and creates related symlink action. For
* artifacts that should retain filename (e.g. libraries with SONAME tag), link is created to the
* parent directory instead.
*
* <p>This action is performed to minimize number of -rpath entries used during linking process
* (by essentially "collecting" as many shared libraries as possible in the single directory),
* since we will be paying quadratic price for each additional entry on the -rpath.
*
* @param actionRegistry action registry of rule requesting symlink.
* @param actionConstructionContext action construction context of rule requesting symlink
* @param solibDir String giving the solib directory
* @param library Shared library artifact that needs to be mangled.
* @param preserveName whether to preserve the name of the library
* @param prefixConsumer whether to prefix the output artifact name with the label of the consumer
* @return mangled symlink artifact.
*/
public static Artifact getDynamicLibrarySymlink(
ActionRegistry actionRegistry,
ActionConstructionContext actionConstructionContext,
String solibDir,
final Artifact library,
boolean preserveName,
boolean prefixConsumer) {
PathFragment mangledName =
getMangledName(
actionRegistry.getOwner().getLabel(),
solibDir,
library.getRootRelativePath(),
preserveName,
prefixConsumer);
return getDynamicLibrarySymlinkInternal(
actionRegistry, actionConstructionContext, library, mangledName);
}
/**
* Replaces shared library artifact with user specified symlink and creates related symlink
* action.
*
* <p>This action is performed to minimize number of -rpath entries used during linking process
* (by essentially "collecting" as many shared libraries as possible in the single directory),
* since we will be paying quadratic price for each additional entry on the -rpath.
*
* @param actionRegistry action registry of rule requesting symlink.
* @param actionConstructionContext action construction context of rule requesting symlink
* @param solibDir String giving the solib directory
* @param library Shared library artifact that needs to be linked.
* @param path Symlink path underneath the solib directory.
* @return linked symlink artifact.
*/
public static Artifact getDynamicLibrarySymlink(
ActionRegistry actionRegistry,
ActionConstructionContext actionConstructionContext,
String solibDir,
final Artifact library,
PathFragment path) {
Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(library.getFilename()));
Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(path.getBaseName()));
Preconditions.checkArgument(!library.getRootRelativePath().getSegment(0).startsWith("_solib_"));
PathFragment solibDirPath = PathFragment.create(solibDir);
PathFragment linkName = solibDirPath.getRelative(path);
return getDynamicLibrarySymlinkInternal(
actionRegistry, actionConstructionContext, library, linkName);
}
/**
* Version of {@link #getDynamicLibrarySymlink} for the special case of C++ runtime libraries.
* These are handled differently than other libraries: neither their names nor directories are
* mangled, i.e. libstdc++.so.6 is symlinked from _solib_[arch]/libstdc++.so.6
*/
public static Artifact getCppRuntimeSymlink(
RuleContext ruleContext,
Artifact library,
String toolchainProvidedSolibDir,
String solibDirOverride) {
PathFragment solibDir =
PathFragment.create(
solibDirOverride != null ? solibDirOverride : toolchainProvidedSolibDir);
PathFragment symlinkName = solibDir.getRelative(library.getRootRelativePath().getBaseName());
return getDynamicLibrarySymlinkInternal(
/* actionRegistry= */ ruleContext,
/* actionConstructionContext= */ ruleContext,
library,
symlinkName);
}
/**
* Internal implementation that takes a pre-determined symlink name; supports both the generic
* {@link #getDynamicLibrarySymlink} and the specialized {@link #getCppRuntimeSymlink}.
*/
private static Artifact getDynamicLibrarySymlinkInternal(
ActionRegistry actionRegistry,
ActionConstructionContext actionConstructionContext,
Artifact library,
PathFragment symlinkName) {
Preconditions.checkArgument(Link.SHARED_LIBRARY_FILETYPES.matches(library.getFilename()));
Preconditions.checkArgument(!library.getRootRelativePath().getSegment(0).startsWith("_solib_"));
// Ignore libraries that are already represented by the symlinks.
ArtifactRoot root = actionConstructionContext.getBinDirectory();
Artifact symlink = actionConstructionContext.getShareableArtifact(symlinkName, root);
actionRegistry.registerAction(
new SolibSymlinkAction(actionConstructionContext.getActionOwner(), library, symlink));
return symlink;
}
/**
* Returns the name of the symlink that will be created for a library, given its name.
*
* @param label label of the rule calling this
* @param solibDir a String giving the solib directory
* @param libraryPath the root-relative path of the library
* @param preserveName true if filename should be preserved
* @param prefixConsumer true if the result should be prefixed with the label of the consumer
* @returns root relative path name
*/
private static PathFragment getMangledName(
Label label,
String solibDir,
PathFragment libraryPath,
boolean preserveName,
boolean prefixConsumer) {
String escapedRulePath = Actions.escapedPath("_" + label);
String soname = getDynamicLibrarySoname(libraryPath, preserveName);
PathFragment solibDirPath = PathFragment.create(solibDir);
if (preserveName) {
String escapedLibraryPath =
Actions.escapedPath("_" + libraryPath.getParentDirectory().getPathString());
PathFragment mangledDir =
solibDirPath.getRelative(
prefixConsumer ? escapedRulePath + "__" + escapedLibraryPath : escapedLibraryPath);
return mangledDir.getRelative(soname);
} else {
return solibDirPath.getRelative(prefixConsumer ? escapedRulePath + "__" + soname : soname);
}
}
/**
* Compute the SONAME to use for a dynamic library. This name is basically the
* name of the shared library in its final symlinked location.
*
* @param libraryPath name of the shared library that needs to be mangled
* @param preserveName true if filename should be preserved, false - mangled
* @return soname to embed in the dynamic library
*/
public static String getDynamicLibrarySoname(PathFragment libraryPath,
boolean preserveName) {
String mangledName;
if (preserveName) {
mangledName = libraryPath.getBaseName();
} else {
mangledName = "lib" + Actions.escapedPath(libraryPath.getPathString());
}
return mangledName;
}
@Override
public boolean shouldReportPathPrefixConflict(ActionAnalysisMetadata action) {
return false; // Always ignore path prefix conflict for the SolibSymlinkAction.
}
@Override
public boolean mayInsensitivelyPropagateInputs() {
return true;
}
}