/* * Copyright 2014-present Facebook, Inc. * * 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.facebook.buck.cxx; import com.facebook.buck.io.MorePaths; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.args.SanitizedArg; import com.facebook.buck.rules.args.SourcePathArg; import com.facebook.buck.rules.args.StringArg; import com.facebook.buck.rules.coercer.FrameworkPath; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import java.nio.file.Path; import java.util.EnumSet; import java.util.Objects; import java.util.Optional; public class CxxLinkableEnhancer { private static final Logger LOG = Logger.get(CxxLinkableEnhancer.class); private static final EnumSet<Linker.LinkType> SONAME_REQUIRED_LINK_TYPES = EnumSet.of(Linker.LinkType.SHARED, Linker.LinkType.MACH_O_BUNDLE); // Utility class doesn't instantiate. private CxxLinkableEnhancer() {} /** @param params base params used to build the rule. Target and deps will be overridden. */ public static CxxLink createCxxLinkableBuildRule( CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform, BuildRuleParams params, BuildRuleResolver ruleResolver, SourcePathRuleFinder ruleFinder, BuildTarget target, Path output, ImmutableList<Arg> args, Linker.LinkableDepType depType, boolean thinLto, Optional<Linker.CxxRuntimeType> cxxRuntimeType, Optional<LinkOutputPostprocessor> postprocessor) { final Linker linker = cxxPlatform.getLd().resolve(ruleResolver); // Build up the arguments to pass to the linker. ImmutableList.Builder<Arg> argsBuilder = ImmutableList.builder(); // Add flags to generate linker map if supported. if (linker instanceof HasLinkerMap && LinkerMapMode.isLinkerMapEnabledForBuildTarget(target)) { argsBuilder.addAll(((HasLinkerMap) linker).linkerMap(output)); } // Add lto object path if thin LTO is on. if (linker instanceof HasThinLTO && thinLto) { argsBuilder.addAll(((HasThinLTO) linker).thinLTO(output)); } // Pass any platform specific or extra linker flags. argsBuilder.addAll( SanitizedArg.from( cxxPlatform.getCompilerDebugPathSanitizer().sanitize(Optional.empty()), cxxPlatform.getLdflags())); argsBuilder.addAll(args); // Add all arguments needed to link in the C/C++ platform runtime. Linker.LinkableDepType runtimeDepType = depType; if (cxxRuntimeType.orElse(Linker.CxxRuntimeType.DYNAMIC) == Linker.CxxRuntimeType.STATIC) { runtimeDepType = Linker.LinkableDepType.STATIC; } argsBuilder.addAll(StringArg.from(cxxPlatform.getRuntimeLdflags().get(runtimeDepType))); final ImmutableList<Arg> allArgs = argsBuilder.build(); // Build the C/C++ link step. Supplier<ImmutableSortedSet<BuildRule>> declaredDeps = () -> FluentIterable.from(allArgs) .transformAndConcat(arg -> arg.getDeps(ruleFinder)) .append(linker.getDeps(ruleFinder)) .toSortedSet(Ordering.natural()); return new CxxLink( // Construct our link build rule params. The important part here is combining the build // rules that construct our object file inputs and also the deps that build our // dependencies. params .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( declaredDeps, Suppliers.ofInstance(ImmutableSortedSet.of())), linker, output, allArgs, postprocessor, cxxBuckConfig.getLinkScheduleInfo(), cxxBuckConfig.shouldCacheLinks(), thinLto); } /** * Construct a {@link CxxLink} rule that builds a native linkable from top-level input objects and * a dependency tree of {@link NativeLinkable} dependencies. * * @param params base params used to build the rule. Target and deps will be overridden. * @param nativeLinkableDeps library dependencies that the linkable links in * @param immediateLinkableInput framework and libraries of the linkable itself */ public static CxxLink createCxxLinkableBuildRule( CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform, BuildRuleParams params, BuildRuleResolver ruleResolver, final SourcePathResolver resolver, SourcePathRuleFinder ruleFinder, BuildTarget target, Linker.LinkType linkType, Optional<String> soname, Path output, Linker.LinkableDepType depType, boolean thinLto, Iterable<? extends NativeLinkable> nativeLinkableDeps, Optional<Linker.CxxRuntimeType> cxxRuntimeType, Optional<SourcePath> bundleLoader, ImmutableSet<BuildTarget> blacklist, NativeLinkableInput immediateLinkableInput, Optional<LinkOutputPostprocessor> postprocessor) throws NoSuchBuildTargetException { // Soname should only ever be set when linking a "shared" library. Preconditions.checkState(!soname.isPresent() || SONAME_REQUIRED_LINK_TYPES.contains(linkType)); // Bundle loaders are only supported for Mach-O bundle libraries Preconditions.checkState( !bundleLoader.isPresent() || linkType == Linker.LinkType.MACH_O_BUNDLE); // Collect and topologically sort our deps that contribute to the link. ImmutableList.Builder<NativeLinkableInput> nativeLinkableInputs = ImmutableList.builder(); nativeLinkableInputs.add(immediateLinkableInput); for (NativeLinkable nativeLinkable : Maps.filterKeys( NativeLinkables.getNativeLinkables(cxxPlatform, nativeLinkableDeps, depType), Predicates.not(blacklist::contains)) .values()) { NativeLinkableInput input = NativeLinkables.getNativeLinkableInput(cxxPlatform, depType, nativeLinkable); LOG.verbose("Native linkable %s returned input %s", nativeLinkable, input); nativeLinkableInputs.add(input); } NativeLinkableInput linkableInput = NativeLinkableInput.concat(nativeLinkableInputs.build()); // Build up the arguments to pass to the linker. ImmutableList.Builder<Arg> argsBuilder = ImmutableList.builder(); // If we're doing a shared build, pass the necessary flags to the linker, including setting // the soname. if (linkType == Linker.LinkType.SHARED) { argsBuilder.addAll(cxxPlatform.getLd().resolve(ruleResolver).getSharedLibFlag()); } else if (linkType == Linker.LinkType.MACH_O_BUNDLE) { argsBuilder.add(StringArg.of("-bundle")); // It's possible to build a Mach-O bundle without a bundle loader (logic tests, for example). if (bundleLoader.isPresent()) { argsBuilder.add(StringArg.of("-bundle_loader"), SourcePathArg.of(bundleLoader.get())); } } if (soname.isPresent()) { argsBuilder.addAll( StringArg.from(cxxPlatform.getLd().resolve(ruleResolver).soname(soname.get()))); } // Add all arguments from our dependencies. argsBuilder.addAll(linkableInput.getArgs()); // Add all shared libraries if (!linkableInput.getLibraries().isEmpty()) { addSharedLibrariesLinkerArgs( cxxPlatform, resolver, ImmutableSortedSet.copyOf(linkableInput.getLibraries()), argsBuilder); } // Add framework args if (!linkableInput.getFrameworks().isEmpty()) { addFrameworkLinkerArgs( cxxPlatform, resolver, ImmutableSortedSet.copyOf(linkableInput.getFrameworks()), argsBuilder); } final ImmutableList<Arg> allArgs = argsBuilder.build(); return createCxxLinkableBuildRule( cxxBuckConfig, cxxPlatform, params, ruleResolver, ruleFinder, target, output, allArgs, depType, thinLto, cxxRuntimeType, postprocessor); } private static void addSharedLibrariesLinkerArgs( CxxPlatform cxxPlatform, SourcePathResolver resolver, ImmutableSortedSet<FrameworkPath> allLibraries, ImmutableList.Builder<Arg> argsBuilder) { final Function<FrameworkPath, Path> frameworkPathToSearchPath = CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, resolver); argsBuilder.add( new FrameworkPathArg(allLibraries) { @Override public void appendToRuleKey(RuleKeyObjectSink sink) { super.appendToRuleKey(sink); sink.setReflectively("frameworkPathToSearchPath", frameworkPathToSearchPath); } @Override public void appendToCommandLine( ImmutableCollection.Builder<String> builder, SourcePathResolver pathResolver) { ImmutableSortedSet<Path> searchPaths = FluentIterable.from(frameworkPaths) .transform(frameworkPathToSearchPath) .filter(Objects::nonNull) .toSortedSet(Ordering.natural()); for (Path searchPath : searchPaths) { builder.add("-L"); builder.add(searchPath.toString()); } } }); // Add all libraries link args argsBuilder.add( new FrameworkPathArg(allLibraries) { @Override public void appendToCommandLine( ImmutableCollection.Builder<String> builder, SourcePathResolver pathResolver) { for (FrameworkPath frameworkPath : frameworkPaths) { String libName = MorePaths.stripPathPrefixAndExtension( frameworkPath.getFileName(resolver::getAbsolutePath), "lib"); // libraries set can contain path-qualified libraries, or just library // search paths. // Assume these end in '../lib' and filter out here. if (libName.isEmpty()) { continue; } builder.add("-l" + libName); } } }); } private static void addFrameworkLinkerArgs( CxxPlatform cxxPlatform, SourcePathResolver resolver, ImmutableSortedSet<FrameworkPath> allFrameworks, ImmutableList.Builder<Arg> argsBuilder) { final Function<FrameworkPath, Path> frameworkPathToSearchPath = CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, resolver); argsBuilder.add( new FrameworkPathArg(allFrameworks) { @Override public void appendToRuleKey(RuleKeyObjectSink sink) { super.appendToRuleKey(sink); sink.setReflectively("frameworkPathToSearchPath", frameworkPathToSearchPath); } @Override public void appendToCommandLine( ImmutableCollection.Builder<String> builder, SourcePathResolver pathResolver) { ImmutableSortedSet<Path> searchPaths = FluentIterable.from(frameworkPaths) .transform(frameworkPathToSearchPath) .toSortedSet(Ordering.natural()); for (Path searchPath : searchPaths) { builder.add("-F"); builder.add(searchPath.toString()); } } }); // Add all framework link args argsBuilder.add(frameworksToLinkerArg(allFrameworks)); } @VisibleForTesting static Arg frameworksToLinkerArg(ImmutableSortedSet<FrameworkPath> frameworkPaths) { return new FrameworkPathArg(frameworkPaths) { @Override public void appendToCommandLine( ImmutableCollection.Builder<String> builder, SourcePathResolver pathResolver) { for (FrameworkPath frameworkPath : frameworkPaths) { builder.add("-framework"); builder.add(frameworkPath.getName(pathResolver::getAbsolutePath)); } } }; } public static CxxLink createCxxLinkableSharedBuildRule( CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform, BuildRuleParams params, BuildRuleResolver ruleResolver, SourcePathRuleFinder ruleFinder, BuildTarget target, Path output, Optional<String> soname, ImmutableList<? extends Arg> args) { ImmutableList.Builder<Arg> linkArgsBuilder = ImmutableList.builder(); linkArgsBuilder.addAll(cxxPlatform.getLd().resolve(ruleResolver).getSharedLibFlag()); if (soname.isPresent()) { linkArgsBuilder.addAll( StringArg.from(cxxPlatform.getLd().resolve(ruleResolver).soname(soname.get()))); } linkArgsBuilder.addAll(args); ImmutableList<Arg> linkArgs = linkArgsBuilder.build(); return createCxxLinkableBuildRule( cxxBuckConfig, cxxPlatform, params, ruleResolver, ruleFinder, target, output, linkArgs, Linker.LinkableDepType.SHARED, /* thinLto */ false, Optional.empty(), Optional.empty()); } }