com.facebook.buck.cxx.CxxLinkableEnhancer.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.cxx.CxxLinkableEnhancer.java

Source

/*
 * 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.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.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, final SourcePathResolver resolver,
            SourcePathRuleFinder ruleFinder, BuildTarget target, Path output, ImmutableList<Arg> args,
            Linker.LinkableDepType depType, Optional<Linker.CxxRuntimeType> cxxRuntimeType) {

        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));
        }

        // 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.
        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.copyWithChanges(target,
                        () -> FluentIterable.from(allArgs).transformAndConcat(arg -> arg.getDeps(ruleFinder))
                                .append(linker.getDeps(ruleFinder)).toSortedSet(Ordering.natural()),
                        Suppliers.ofInstance(ImmutableSortedSet.of())),
                resolver, linker, output, allArgs, cxxBuckConfig.getLinkScheduleInfo(),
                cxxBuckConfig.shouldCacheLinks());
    }

    /**
     * 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, Iterable<? extends NativeLinkable> nativeLinkableDeps,
            Optional<Linker.CxxRuntimeType> cxxRuntimeType, Optional<SourcePath> bundleLoader,
            ImmutableSet<BuildTarget> blacklist, NativeLinkableInput immediateLinkableInput)
            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(new StringArg("-bundle"));
            // It's possible to build a Mach-O bundle without a bundle loader (logic tests, for example).
            if (bundleLoader.isPresent()) {
                argsBuilder.add(new StringArg("-bundle_loader"), new SourcePathArg(resolver, 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
        addSharedLibrariesLinkerArgs(cxxPlatform, resolver, ImmutableSortedSet.copyOf(linkableInput.getLibraries()),
                argsBuilder);

        // Add framework args
        addFrameworkLinkerArgs(cxxPlatform, resolver, ImmutableSortedSet.copyOf(linkableInput.getFrameworks()),
                argsBuilder);

        final ImmutableList<Arg> allArgs = argsBuilder.build();

        return createCxxLinkableBuildRule(cxxBuckConfig, cxxPlatform, params, ruleResolver, resolver, ruleFinder,
                target, output, allArgs, depType, cxxRuntimeType);
    }

    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(resolver, allLibraries) {

            @Override
            public void appendToRuleKey(RuleKeyObjectSink sink) {
                super.appendToRuleKey(sink);
                sink.setReflectively("frameworkPathToSearchPath", frameworkPathToSearchPath);
            }

            @Override
            public void appendToCommandLine(ImmutableCollection.Builder<String> builder) {
                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(resolver, allLibraries) {
            @Override
            public void appendToCommandLine(ImmutableCollection.Builder<String> builder) {
                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(resolver, allFrameworks) {
            @Override
            public void appendToRuleKey(RuleKeyObjectSink sink) {
                super.appendToRuleKey(sink);
                sink.setReflectively("frameworkPathToSearchPath", frameworkPathToSearchPath);
            }

            @Override
            public void appendToCommandLine(ImmutableCollection.Builder<String> builder) {
                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(resolver, allFrameworks));
    }

    @VisibleForTesting
    static Arg frameworksToLinkerArg(final SourcePathResolver resolver,
            ImmutableSortedSet<FrameworkPath> frameworkPaths) {
        return new FrameworkPathArg(resolver, frameworkPaths) {
            @Override
            public void appendToCommandLine(ImmutableCollection.Builder<String> builder) {
                for (FrameworkPath frameworkPath : frameworkPaths) {
                    builder.add("-framework");
                    builder.add(frameworkPath.getName(resolver::getAbsolutePath));
                }
            }
        };
    }

    public static CxxLink createCxxLinkableSharedBuildRule(CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform,
            BuildRuleParams params, BuildRuleResolver ruleResolver, final SourcePathResolver resolver,
            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, resolver, ruleFinder,
                target, output, linkArgs, Linker.LinkableDepType.SHARED, Optional.empty());
    }

}