Java tutorial
// 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.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue; import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder; import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType; import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.HashMap; import java.util.Map; /** Class that goes over linker inputs and produces {@link LibraryToLinkValue}s */ public class LibrariesToLinkCollector { private final boolean isNativeDeps; private final PathFragment toolchainLibrariesSolibDir; private final CppConfiguration cppConfiguration; private final CcToolchainProvider ccToolchainProvider; private final Artifact outputArtifact; private final boolean isLtoIndexing; private final PathFragment solibDir; private final Iterable<? extends LinkerInput> linkerInputs; private final Iterable<LtoBackendArtifacts> allLtoArtifacts; private final boolean allowLtoIndexing; private final Artifact thinltoParamFile; private final FeatureConfiguration featureConfiguration; private final boolean needWholeArchive; private final String rpathRoot; private final boolean needToolchainLibrariesRpath; private final Map<Artifact, Artifact> ltoMap; public LibrariesToLinkCollector(boolean isNativeDeps, CppConfiguration cppConfiguration, CcToolchainProvider toolchain, PathFragment toolchainLibrariesSolibDir, LinkTargetType linkType, Link.LinkingMode linkingMode, Artifact output, PathFragment solibDir, boolean isLtoIndexing, Iterable<LtoBackendArtifacts> allLtoArtifacts, FeatureConfiguration featureConfiguration, Artifact thinltoParamFile, boolean allowLtoIndexing, Iterable<LinkerInput> linkerInputs, boolean needWholeArchive) { this.isNativeDeps = isNativeDeps; this.cppConfiguration = cppConfiguration; this.ccToolchainProvider = toolchain; this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir; this.outputArtifact = output; this.solibDir = solibDir; this.isLtoIndexing = isLtoIndexing; this.allLtoArtifacts = allLtoArtifacts; this.featureConfiguration = featureConfiguration; this.thinltoParamFile = thinltoParamFile; this.allowLtoIndexing = allowLtoIndexing; this.linkerInputs = linkerInputs; this.needWholeArchive = needWholeArchive; needToolchainLibrariesRpath = toolchainLibrariesSolibDir != null && (linkType.isDynamicLibrary() || (linkType == LinkTargetType.EXECUTABLE && linkingMode == LinkingMode.DYNAMIC)); // Calculate the correct relative value for the "-rpath" link option (which sets // the search path for finding shared libraries). if (isNativeDeps && cppConfiguration.shareNativeDeps()) { // For shared native libraries, special symlinking is applied to ensure C++ // toolchain libraries are available under $ORIGIN/_solib_[arch]. So we set the RPATH to find // them. // // Note that we have to do this because $ORIGIN points to different paths for // different targets. In other words, blaze-bin/d1/d2/d3/a_shareddeps.so and // blaze-bin/d4/b_shareddeps.so have different path depths. The first could // reference a standard blaze-bin/_solib_[arch] via $ORIGIN/../../../_solib[arch], // and the second could use $ORIGIN/../_solib_[arch]. But since this is a shared // artifact, both are symlinks to the same place, so // there's no *one* RPATH setting that fits all targets involved in the sharing. rpathRoot = ccToolchainProvider.getSolibDirectory() + "/"; } else { rpathRoot = Strings.repeat("../", outputArtifact.getRootRelativePath().segmentCount() - 1) + ccToolchainProvider.getSolibDirectory() + "/"; } ltoMap = generateLtoMap(); } /** * Result of {@link LibrariesToLinkCollector#collectLibrariesToLink()}. Provides access to * computed sequence of {@link LibraryToLinkValue}s and accompanying library search directories. */ public static class CollectedLibrariesToLink { private final SequenceBuilder librariesToLink; private final ImmutableSet<LinkerInput> expandedLinkerInputs; private final ImmutableSet<String> librarySearchDirectories; private final ImmutableSet<String> runtimeLibrarySearchDirectories; public CollectedLibrariesToLink(SequenceBuilder librariesToLink, ImmutableSet<LinkerInput> expandedLinkerInputs, ImmutableSet<String> librarySearchDirectories, ImmutableSet<String> runtimeLibrarySearchDirectories) { this.librariesToLink = librariesToLink; this.expandedLinkerInputs = expandedLinkerInputs; this.librarySearchDirectories = librarySearchDirectories; this.runtimeLibrarySearchDirectories = runtimeLibrarySearchDirectories; } public SequenceBuilder getLibrariesToLink() { return librariesToLink; } // TODO(b/78347840): Figure out how to make these Artifacts. public ImmutableSet<LinkerInput> getExpandedLinkerInputs() { return expandedLinkerInputs; } public ImmutableSet<String> getLibrarySearchDirectories() { return librarySearchDirectories; } public ImmutableSet<String> getRuntimeLibrarySearchDirectories() { return runtimeLibrarySearchDirectories; } } /** * When linking a shared library fully or mostly static then we need to link in *all* dependent * files, not just what the shared library needs for its own code. This is done by wrapping all * objects/libraries with -Wl,-whole-archive and -Wl,-no-whole-archive. For this case the * globalNeedWholeArchive parameter must be set to true. Otherwise only library objects (.lo) need * to be wrapped with -Wl,-whole-archive and -Wl,-no-whole-archive. * * <p>TODO: Factor out of the bazel binary into build variables for crosstool action_configs. */ public CollectedLibrariesToLink collectLibrariesToLink() { ImmutableSet.Builder<String> librarySearchDirectories = ImmutableSet.builder(); ImmutableSet.Builder<String> runtimeLibrarySearchDirectories = ImmutableSet.builder(); ImmutableSet.Builder<String> rpathRootsForExplicitSoDeps = ImmutableSet.builder(); ImmutableSet.Builder<LinkerInput> expandedLinkerInputsBuilder = ImmutableSet.builder(); // List of command line parameters that need to be placed *outside* of // --whole-archive ... --no-whole-archive. SequenceBuilder librariesToLink = new SequenceBuilder(); String toolchainLibrariesSolibName = toolchainLibrariesSolibDir != null ? toolchainLibrariesSolibDir.getBaseName() : null; if (isNativeDeps && cppConfiguration.shareNativeDeps()) { if (needToolchainLibrariesRpath) { runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/"); } } else { // For all other links, calculate the relative path from the output file to _solib_[arch] // (the directory where all shared libraries are stored, which resides under the blaze-bin // directory. In other words, given blaze-bin/my/package/binary, rpathRoot would be // "../../_solib_[arch]". if (needToolchainLibrariesRpath) { runtimeLibrarySearchDirectories .add(Strings.repeat("../", outputArtifact.getRootRelativePath().segmentCount() - 1) + toolchainLibrariesSolibName + "/"); } if (isNativeDeps) { // We also retain the $ORIGIN/ path to solibs that are in _solib_<arch>, as opposed to // the package directory) if (needToolchainLibrariesRpath) { runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/"); } } } if (needToolchainLibrariesRpath) { if (isNativeDeps) { runtimeLibrarySearchDirectories.add("."); } runtimeLibrarySearchDirectories.add(toolchainLibrariesSolibName + "/"); } Pair<Boolean, Boolean> includeSolibsPair = addLinkerInputs(librarySearchDirectories, rpathRootsForExplicitSoDeps, librariesToLink, expandedLinkerInputsBuilder); boolean includeSolibDir = includeSolibsPair.first; boolean includeToolchainLibrariesSolibDir = includeSolibsPair.second; Preconditions.checkState(ltoMap == null || ltoMap.isEmpty(), "Still have LTO objects left: %s", ltoMap); ImmutableSet.Builder<String> allRuntimeLibrarySearchDirectories = ImmutableSet.builder(); // rpath ordering matters for performance; first add the one where most libraries are found. if (includeSolibDir) { allRuntimeLibrarySearchDirectories.add(rpathRoot); } allRuntimeLibrarySearchDirectories.addAll(rpathRootsForExplicitSoDeps.build()); if (includeToolchainLibrariesSolibDir) { allRuntimeLibrarySearchDirectories.addAll(runtimeLibrarySearchDirectories.build()); } return new CollectedLibrariesToLink(librariesToLink, expandedLinkerInputsBuilder.build(), librarySearchDirectories.build(), allRuntimeLibrarySearchDirectories.build()); } private Pair<Boolean, Boolean> addLinkerInputs(ImmutableSet.Builder<String> librarySearchDirectories, ImmutableSet.Builder<String> rpathEntries, SequenceBuilder librariesToLink, ImmutableSet.Builder<LinkerInput> expandedLinkerInputsBuilder) { boolean includeSolibDir = false; boolean includeToolchainLibrariesSolibDir = false; for (LinkerInput input : linkerInputs) { if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY) { PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); // When COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, dynamic libraries are not symlinked // under solibDir, so don't check it and don't include solibDir. if (!featureConfiguration.isEnabled(CppRuleClasses.COPY_DYNAMIC_LIBRARIES_TO_BINARY)) { Preconditions.checkState( libDir.startsWith(solibDir) || libDir.startsWith(toolchainLibrariesSolibDir), "Artifact '%s' is not under directory expected '%s'," + " neither it is in directory for toolchain libraries '%'.", input.getArtifact(), solibDir, toolchainLibrariesSolibDir); if (libDir.equals(solibDir)) { includeSolibDir = true; } if (libDir.equals(toolchainLibrariesSolibDir)) { includeToolchainLibrariesSolibDir = true; } } addDynamicInputLinkOptions(input, librariesToLink, expandedLinkerInputsBuilder, librarySearchDirectories, rpathEntries); } else { addStaticInputLinkOptions(input, librariesToLink, expandedLinkerInputsBuilder); } } return Pair.of(includeSolibDir, includeToolchainLibrariesSolibDir); } /** * Adds command-line options for a dynamic library input file into options and libOpts. * * @param librariesToLink - a collection that will be exposed as a build variable. */ private void addDynamicInputLinkOptions(LinkerInput input, SequenceBuilder librariesToLink, ImmutableSet.Builder<LinkerInput> expandedLinkerInputsBuilder, ImmutableSet.Builder<String> librarySearchDirectories, ImmutableSet.Builder<String> rpathRootsForExplicitSoDeps) { Preconditions.checkState(input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY); Preconditions.checkState( !Link.useStartEndLib(input, CppHelper.getArchiveType(cppConfiguration, ccToolchainProvider))); expandedLinkerInputsBuilder.add(input); Artifact inputArtifact = input.getArtifact(); PathFragment libDir = inputArtifact.getExecPath().getParentDirectory(); if (!libDir.equals(solibDir) && (toolchainLibrariesSolibDir == null || !toolchainLibrariesSolibDir.equals(libDir))) { String dotdots = ""; PathFragment commonParent = solibDir; while (!libDir.startsWith(commonParent)) { dotdots += "../"; commonParent = commonParent.getParentDirectory(); } rpathRootsForExplicitSoDeps.add(rpathRoot + dotdots + libDir.relativeTo(commonParent).getPathString()); } librarySearchDirectories.add(inputArtifact.getExecPath().getParentDirectory().getPathString()); String name = inputArtifact.getFilename(); if (CppFileTypes.SHARED_LIBRARY.matches(name)) { // Use normal shared library resolution rules for shared libraries. String libName = name.replaceAll("(^lib|\\.(so|dylib)$)", ""); librariesToLink.addValue(LibraryToLinkValue.forDynamicLibrary(libName)); } else if (CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name)) { // Versioned shared libraries require the exact library filename, e.g.: // -lfoo -> libfoo.so // -l:libfoo.so.1 -> libfoo.so.1 librariesToLink.addValue(LibraryToLinkValue.forVersionedDynamicLibrary(name)); } else { // Interface shared objects have a non-standard extension // that the linker won't be able to find. So use the // filename directly rather than a -l option. Since the // library has an SONAME attribute, this will work fine. librariesToLink.addValue(LibraryToLinkValue.forInterfaceLibrary(inputArtifact.getExecPathString())); } } /** * Get the effective path for the object artifact, being careful not to create new strings if * possible. * * @param: objectFile - Artifact representing an object file. * @param -inputIsFake - Is this object being used for a cc_fake_binary. */ private static String effectiveObjectFilePath(Artifact objectFile, boolean inputIsFake) { // Avoid making new strings as much as possible! This called for every object file used in the // build. if (inputIsFake) { return Link.FAKE_OBJECT_PREFIX + objectFile.getExecPathString(); } return objectFile.getExecPathString(); } /** * Adds command-line options for a static library or non-library input into options. * * @param librariesToLink - a collection that will be exposed as a build variable. */ private void addStaticInputLinkOptions(LinkerInput input, SequenceBuilder librariesToLink, ImmutableSet.Builder<LinkerInput> expandedLinkerInputsBuilder) { ArtifactCategory artifactCategory = input.getArtifactCategory(); Preconditions.checkArgument(artifactCategory.equals(ArtifactCategory.OBJECT_FILE) || artifactCategory.equals(ArtifactCategory.STATIC_LIBRARY) || artifactCategory.equals(ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY)); boolean isAlwaysLinkStaticLibrary = artifactCategory == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY; // input.disableWholeArchive() should only be true for libstdc++/libc++ etc. boolean inputIsWholeArchive = !input.disableWholeArchive() && (isAlwaysLinkStaticLibrary || needWholeArchive); // If we had any LTO artifacts, ltoMap whould be non-null. In that case, // we should have created a thinltoParamFile which the LTO indexing // step will populate with the exec paths that correspond to the LTO // artifacts that the linker decided to include based on symbol resolution. // Those files will be included directly in the link (and not wrapped // in --start-lib/--end-lib) to ensure consistency between the two link // steps. Preconditions.checkState(ltoMap == null || thinltoParamFile != null || !allowLtoIndexing); // start-lib/end-lib library: adds its input object files. if (Link.useStartEndLib(input, CppHelper.getArchiveType(cppConfiguration, ccToolchainProvider))) { Iterable<Artifact> archiveMembers = input.getObjectFiles(); if (!Iterables.isEmpty(archiveMembers)) { ImmutableList.Builder<Artifact> nonLtoArchiveMembersBuilder = ImmutableList.builder(); for (Artifact member : archiveMembers) { Artifact a; if (ltoMap != null && (a = ltoMap.remove(member)) != null) { // When ltoMap is non-null the backend artifact may be missing due to libraries that // list .o files explicitly, or generate .o files from assembler. if (handledByLtoIndexing(a, allowLtoIndexing)) { // The LTO artifacts that should be included in the final link // are listed in the thinltoParamFile, generated by the LTO indexing. // Even if this object file is being skipped for exposure as a Build variable, it's // still an input to this action. expandedLinkerInputsBuilder.add(LinkerInputs.simpleLinkerInput(a, ArtifactCategory.OBJECT_FILE, /* disableWholeArchive= */ false)); continue; } // No LTO indexing step, so use the LTO backend's generated artifact directly // instead of the bitcode object. member = a; } nonLtoArchiveMembersBuilder.add(member); expandedLinkerInputsBuilder.add(LinkerInputs.simpleLinkerInput(member, ArtifactCategory.OBJECT_FILE, /* disableWholeArchive = */ false)); } ImmutableList<Artifact> nonLtoArchiveMembers = nonLtoArchiveMembersBuilder.build(); if (!nonLtoArchiveMembers.isEmpty()) { if (inputIsWholeArchive) { for (Artifact member : nonLtoArchiveMembers) { if (member.isTreeArtifact()) { // TODO(b/78189629): This object filegroup is expanded at action time but wrapped // with --start/--end-lib. There's currently no way to force these objects to be // linked in. librariesToLink.addValue(LibraryToLinkValue.forObjectFileGroup( ImmutableList.<Artifact>of(member), /* isWholeArchive= */ true)); } else { // TODO(b/78189629): These each need to be their own LibraryToLinkValue so they're // not wrapped in --start/--end-lib (which lets the linker leave out objects with // unreferenced code). librariesToLink.addValue(LibraryToLinkValue.forObjectFile( effectiveObjectFilePath(member, input.isFake()), /* isWholeArchive= */ true)); } } } else { librariesToLink.addValue(LibraryToLinkValue.forObjectFileGroup(nonLtoArchiveMembers, /* isWholeArchive= */ false)); } } } } else { Artifact inputArtifact = input.getArtifact(); Artifact a; if (ltoMap != null && (a = ltoMap.remove(inputArtifact)) != null) { if (handledByLtoIndexing(a, allowLtoIndexing)) { // The LTO artifacts that should be included in the final link // are listed in the thinltoParamFile, generated by the LTO indexing. // Even if this object file is being skipped for exposure as a build variable, it's // still an input to this action. expandedLinkerInputsBuilder.add(LinkerInputs.simpleLinkerInput(a, ArtifactCategory.OBJECT_FILE, /* disableWholeArchive= */ false)); return; } // No LTO indexing step, so use the LTO backend's generated artifact directly // instead of the bitcode object. inputArtifact = a; } if (artifactCategory.equals(ArtifactCategory.OBJECT_FILE)) { if (inputArtifact.isTreeArtifact()) { librariesToLink.addValue(LibraryToLinkValue .forObjectFileGroup(ImmutableList.<Artifact>of(inputArtifact), inputIsWholeArchive)); } else { librariesToLink.addValue(LibraryToLinkValue.forObjectFile( effectiveObjectFilePath(inputArtifact, input.isFake()), inputIsWholeArchive)); } expandedLinkerInputsBuilder.add(input); } else { librariesToLink.addValue(LibraryToLinkValue.forStaticLibrary( effectiveObjectFilePath(inputArtifact, input.isFake()), inputIsWholeArchive)); expandedLinkerInputsBuilder.add(input); } } } /** * Returns true if this artifact is produced from a bitcode file that will be input to the LTO * indexing step, in which case that step will add it to the generated thinltoParamFile for * inclusion in the final link step if the linker decides to include it. * * @param a is an artifact produced by an LTO backend. * @param allowLtoIndexing */ private static boolean handledByLtoIndexing(Artifact a, boolean allowLtoIndexing) { // If no LTO indexing is allowed for this link, then none are handled by LTO indexing. // Otherwise, this may be from a linkstatic library that we decided not to include in // LTO indexing because we are linking a test, to improve scalability when linking many tests. return allowLtoIndexing && !a.getRootRelativePath() .startsWith(PathFragment.create(CppLinkActionBuilder.SHARED_NONLTO_BACKEND_ROOT_PREFIX)); } private Map<Artifact, Artifact> generateLtoMap() { if (isLtoIndexing || allLtoArtifacts == null) { return null; } // TODO(bazel-team): The LTO final link can only work if there are individual .o files on // the command line. Rather than crashing, this should issue a nice error. We will get // this by // 1) moving supports_start_end_lib to a toolchain feature // 2) having thin_lto require start_end_lib // As a bonus, we can rephrase --nostart_end_lib as --features=-start_end_lib and get rid // of a command line option. Preconditions.checkState(CppHelper.useStartEndLib(cppConfiguration, ccToolchainProvider)); Map<Artifact, Artifact> ltoMap = new HashMap<>(); for (LtoBackendArtifacts l : allLtoArtifacts) { ltoMap.put(l.getBitcodeFile(), l.getObjectFile()); } return ltoMap; } }