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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2013-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.ProjectFilesystem;
import com.facebook.buck.json.JsonConcatenate;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.model.ImmutableFlavor;
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.BuildTargetSourcePath;
import com.facebook.buck.rules.CommandTool;
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.SourceWithFlags;
import com.facebook.buck.rules.SymlinkTree;
import com.facebook.buck.rules.args.Arg;
import com.facebook.buck.rules.args.FileListableLinkerInputArg;
import com.facebook.buck.rules.args.MacroArg;
import com.facebook.buck.rules.args.RuleKeyAppendableFunction;
import com.facebook.buck.rules.args.SourcePathArg;
import com.facebook.buck.rules.args.StringArg;
import com.facebook.buck.rules.coercer.FrameworkPath;
import com.facebook.buck.rules.coercer.PatternMatchedCollection;
import com.facebook.buck.rules.coercer.SourceList;
import com.facebook.buck.rules.macros.LocationMacroExpander;
import com.facebook.buck.rules.macros.MacroHandler;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.RichStream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.io.Files;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;

public class CxxDescriptionEnhancer {

    private static final Logger LOG = Logger.get(CxxDescriptionEnhancer.class);

    public static final Flavor SANDBOX_TREE_FLAVOR = ImmutableFlavor.of("sandbox");
    public static final Flavor HEADER_SYMLINK_TREE_FLAVOR = ImmutableFlavor.of("private-headers");
    public static final Flavor EXPORTED_HEADER_SYMLINK_TREE_FLAVOR = ImmutableFlavor.of("headers");
    public static final Flavor STATIC_FLAVOR = ImmutableFlavor.of("static");
    public static final Flavor STATIC_PIC_FLAVOR = ImmutableFlavor.of("static-pic");
    public static final Flavor SHARED_FLAVOR = ImmutableFlavor.of("shared");
    public static final Flavor MACH_O_BUNDLE_FLAVOR = ImmutableFlavor.of("mach-o-bundle");
    public static final Flavor SHARED_LIBRARY_SYMLINK_TREE_FLAVOR = ImmutableFlavor
            .of("shared-library-symlink-tree");

    public static final Flavor CXX_LINK_BINARY_FLAVOR = ImmutableFlavor.of("binary");

    protected static final MacroHandler MACRO_HANDLER = new MacroHandler(
            ImmutableMap.of("location", new LocationMacroExpander()));

    private static final Pattern SONAME_EXT_MACRO_PATTERN = Pattern.compile("\\$\\(ext(?: ([.0-9]+))?\\)");

    private CxxDescriptionEnhancer() {
    }

    public static HeaderSymlinkTree createHeaderSymlinkTree(BuildRuleParams params, BuildRuleResolver resolver,
            SourcePathResolver pathResolver, CxxPlatform cxxPlatform, ImmutableMap<Path, SourcePath> headers,
            HeaderVisibility headerVisibility, boolean shouldCreateHeadersSymlinks) {

        BuildTarget headerSymlinkTreeTarget = CxxDescriptionEnhancer
                .createHeaderSymlinkTreeTarget(params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility);
        Path headerSymlinkTreeRoot = CxxDescriptionEnhancer.getHeaderSymlinkTreePath(params.getProjectFilesystem(),
                params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility);
        boolean useHeaderMap = (cxxPlatform.getCpp().resolve(resolver).supportsHeaderMaps()
                && cxxPlatform.getCxxpp().resolve(resolver).supportsHeaderMaps());
        CxxPreprocessables.HeaderMode mode = !useHeaderMap ? CxxPreprocessables.HeaderMode.SYMLINK_TREE_ONLY
                : (shouldCreateHeadersSymlinks ? CxxPreprocessables.HeaderMode.SYMLINK_TREE_WITH_HEADER_MAP
                        : CxxPreprocessables.HeaderMode.HEADER_MAP_ONLY);

        return CxxPreprocessables.createHeaderSymlinkTreeBuildRule(pathResolver, headerSymlinkTreeTarget, params,
                headerSymlinkTreeRoot, headers, mode);
    }

    public static SymlinkTree createSandboxSymlinkTree(BuildRuleParams params, SourcePathResolver pathResolver,
            CxxPlatform cxxPlatform, ImmutableMap<Path, SourcePath> map) {
        BuildTarget sandboxSymlinkTreeTarget = CxxDescriptionEnhancer
                .createSandboxSymlinkTreeTarget(params.getBuildTarget(), cxxPlatform.getFlavor());
        Path sandboxSymlinkTreeRoot = CxxDescriptionEnhancer
                .getSandboxSymlinkTreePath(params.getProjectFilesystem(), sandboxSymlinkTreeTarget);

        BuildRuleParams paramsWithoutDeps = params.copyWithChanges(sandboxSymlinkTreeTarget,
                Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of()));

        return new SymlinkTree(paramsWithoutDeps, pathResolver, sandboxSymlinkTreeRoot, map);
    }

    public static HeaderSymlinkTree requireHeaderSymlinkTree(BuildRuleParams params, BuildRuleResolver ruleResolver,
            SourcePathResolver pathResolver, CxxPlatform cxxPlatform, ImmutableMap<Path, SourcePath> headers,
            HeaderVisibility headerVisibility, boolean shouldCreateHeadersSymlinks) {
        BuildRuleParams untypedParams = CxxLibraryDescription.getUntypedParams(params);
        BuildTarget headerSymlinkTreeTarget = CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
                untypedParams.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility);

        // Check the cache...
        Optional<BuildRule> rule = ruleResolver.getRuleOptional(headerSymlinkTreeTarget);
        if (rule.isPresent()) {
            Preconditions.checkState(rule.get() instanceof HeaderSymlinkTree);
            return (HeaderSymlinkTree) rule.get();
        }

        HeaderSymlinkTree symlinkTree = createHeaderSymlinkTree(untypedParams, ruleResolver, pathResolver,
                cxxPlatform, headers, headerVisibility, shouldCreateHeadersSymlinks);

        ruleResolver.addToIndex(symlinkTree);

        return symlinkTree;
    }

    private static SymlinkTree requireSandboxSymlinkTree(BuildRuleParams params, BuildRuleResolver ruleResolver,
            CxxPlatform cxxPlatform) throws NoSuchBuildTargetException {
        BuildRuleParams untypedParams = CxxLibraryDescription.getUntypedParams(params);
        BuildTarget headerSymlinkTreeTarget = CxxDescriptionEnhancer
                .createSandboxSymlinkTreeTarget(untypedParams.getBuildTarget(), cxxPlatform.getFlavor());
        BuildRule rule = ruleResolver.requireRule(headerSymlinkTreeTarget);
        Preconditions.checkState(rule instanceof SymlinkTree,
                rule.getBuildTarget() + " " + rule.getClass().toString());
        return (SymlinkTree) rule;
    }

    /**
     * @return the {@link BuildTarget} to use for the {@link BuildRule} generating the
     *    symlink tree of headers.
     */
    @VisibleForTesting
    public static BuildTarget createHeaderSymlinkTreeTarget(BuildTarget target, Flavor platform,
            HeaderVisibility headerVisibility) {
        return BuildTarget.builder(target).addFlavors(platform)
                .addFlavors(getHeaderSymlinkTreeFlavor(headerVisibility)).build();
    }

    @VisibleForTesting
    public static BuildTarget createSandboxSymlinkTreeTarget(BuildTarget target, Flavor platform) {
        return BuildTarget.builder(target).addFlavors(platform).addFlavors(SANDBOX_TREE_FLAVOR).build();
    }

    /**
     * @return the absolute {@link Path} to use for the symlink tree of headers.
     */
    public static Path getHeaderSymlinkTreePath(ProjectFilesystem filesystem, BuildTarget target, Flavor platform,
            HeaderVisibility headerVisibility) {
        return BuildTargets.getGenPath(filesystem,
                createHeaderSymlinkTreeTarget(target, platform, headerVisibility), "%s");
    }

    public static Path getSandboxSymlinkTreePath(ProjectFilesystem filesystem, BuildTarget target) {
        return BuildTargets.getGenPath(filesystem, target, "%s");
    }

    public static Flavor getHeaderSymlinkTreeFlavor(HeaderVisibility headerVisibility) {
        switch (headerVisibility) {
        case PUBLIC:
            return EXPORTED_HEADER_SYMLINK_TREE_FLAVOR;
        case PRIVATE:
            return HEADER_SYMLINK_TREE_FLAVOR;
        default:
            throw new RuntimeException("Unexpected value of enum ExportMode");
        }
    }

    /**
     * @return a map of header locations to input {@link SourcePath} objects formed by parsing the
     *    input {@link SourcePath} objects for the "headers" parameter.
     */
    public static ImmutableMap<Path, SourcePath> parseHeaders(BuildTarget buildTarget, SourcePathResolver resolver,
            Optional<CxxPlatform> cxxPlatform, CxxConstructorArg args) {
        ImmutableMap.Builder<String, SourcePath> headers = ImmutableMap.builder();
        putAllHeaders(args.headers, headers, resolver, "headers", buildTarget);
        if (cxxPlatform.isPresent()) {
            for (SourceList sourceList : args.platformHeaders
                    .getMatchingValues(cxxPlatform.get().getFlavor().toString())) {
                putAllHeaders(sourceList, headers, resolver, "platform_headers", buildTarget);
            }
        }
        return CxxPreprocessables.resolveHeaderMap(
                args.headerNamespace.map(Paths::get).orElse(buildTarget.getBasePath()), headers.build());
    }

    /**
     * @return a map of header locations to input {@link SourcePath} objects formed by parsing the
     *    input {@link SourcePath} objects for the "exportedHeaders" parameter.
     */
    public static ImmutableMap<Path, SourcePath> parseExportedHeaders(BuildTarget buildTarget,
            SourcePathResolver resolver, Optional<CxxPlatform> cxxPlatform, CxxLibraryDescription.Arg args) {
        ImmutableMap.Builder<String, SourcePath> headers = ImmutableMap.builder();
        putAllHeaders(args.exportedHeaders, headers, resolver, "exported_headers", buildTarget);
        if (cxxPlatform.isPresent()) {
            for (SourceList sourceList : args.exportedPlatformHeaders
                    .getMatchingValues(cxxPlatform.get().getFlavor().toString())) {
                putAllHeaders(sourceList, headers, resolver, "exported_platform_headers", buildTarget);
            }
        }
        return CxxPreprocessables.resolveHeaderMap(
                args.headerNamespace.map(Paths::get).orElse(buildTarget.getBasePath()), headers.build());
    }

    /**
     * Resolves the headers in `sourceList` and puts them into `sources` for the specificed
     * `buildTarget`.
     */
    public static void putAllHeaders(SourceList sourceList, ImmutableMap.Builder<String, SourcePath> sources,
            SourcePathResolver sourcePathResolver, String parameterName, BuildTarget buildTarget) {
        switch (sourceList.getType()) {
        case NAMED:
            sources.putAll(sourceList.getNamedSources().get());
            break;
        case UNNAMED:
            sources.putAll(sourcePathResolver.getSourcePathNames(buildTarget, parameterName,
                    sourceList.getUnnamedSources().get()));
            break;
        }
    }

    /**
     * @return a list {@link CxxSource} objects formed by parsing the input {@link SourcePath}
     *    objects for the "srcs" parameter.
     */
    public static ImmutableMap<String, CxxSource> parseCxxSources(BuildTarget buildTarget,
            SourcePathResolver resolver, CxxPlatform cxxPlatform, CxxConstructorArg args) {
        return parseCxxSources(buildTarget, resolver, cxxPlatform, args.srcs, args.platformSrcs);
    }

    public static ImmutableMap<String, CxxSource> parseCxxSources(BuildTarget buildTarget,
            SourcePathResolver resolver, CxxPlatform cxxPlatform, ImmutableSortedSet<SourceWithFlags> srcs,
            PatternMatchedCollection<ImmutableSortedSet<SourceWithFlags>> platformSrcs) {
        ImmutableMap.Builder<String, SourceWithFlags> sources = ImmutableMap.builder();
        putAllSources(srcs, sources, resolver, buildTarget);
        for (ImmutableSortedSet<SourceWithFlags> sourcesWithFlags : platformSrcs
                .getMatchingValues(cxxPlatform.getFlavor().toString())) {
            putAllSources(sourcesWithFlags, sources, resolver, buildTarget);
        }
        return resolveCxxSources(sources.build());
    }

    private static void putAllSources(ImmutableSortedSet<SourceWithFlags> sourcesWithFlags,
            ImmutableMap.Builder<String, SourceWithFlags> sources, SourcePathResolver pathResolver,
            BuildTarget buildTarget) {
        sources.putAll(pathResolver.getSourcePathNames(buildTarget, "srcs", sourcesWithFlags,
                SourceWithFlags::getSourcePath));
    }

    public static ImmutableList<CxxPreprocessorInput> collectCxxPreprocessorInput(BuildRuleParams params,
            CxxPlatform cxxPlatform, ImmutableMultimap<CxxSource.Type, String> preprocessorFlags,
            ImmutableList<HeaderSymlinkTree> headerSymlinkTrees, ImmutableSet<FrameworkPath> frameworks,
            Iterable<CxxPreprocessorInput> cxxPreprocessorInputFromDeps, ImmutableList<String> includeDirs,
            Optional<SymlinkTree> symlinkTree) throws NoSuchBuildTargetException {

        // Add the private includes of any rules which this rule depends on, and which list this rule as
        // a test.
        BuildTarget targetWithoutFlavor = BuildTarget.of(params.getBuildTarget().getUnflavoredBuildTarget());
        ImmutableList.Builder<CxxPreprocessorInput> cxxPreprocessorInputFromTestedRulesBuilder = ImmutableList
                .builder();
        for (BuildRule rule : params.getDeps()) {
            if (rule instanceof NativeTestable) {
                NativeTestable testable = (NativeTestable) rule;
                if (testable.isTestedBy(targetWithoutFlavor)) {
                    LOG.debug("Adding private includes of tested rule %s to testing rule %s", rule.getBuildTarget(),
                            params.getBuildTarget());
                    cxxPreprocessorInputFromTestedRulesBuilder
                            .add(testable.getCxxPreprocessorInput(cxxPlatform, HeaderVisibility.PRIVATE));

                    // Add any dependent headers
                    cxxPreprocessorInputFromTestedRulesBuilder.addAll(CxxPreprocessables
                            .getTransitiveCxxPreprocessorInput(cxxPlatform, ImmutableList.of(rule)));
                }
            }
        }

        ImmutableList<CxxPreprocessorInput> cxxPreprocessorInputFromTestedRules = cxxPreprocessorInputFromTestedRulesBuilder
                .build();
        LOG.verbose("Rules tested by target %s added private includes %s", params.getBuildTarget(),
                cxxPreprocessorInputFromTestedRules);

        ImmutableList.Builder<CxxHeaders> allIncludes = ImmutableList.builder();
        for (HeaderSymlinkTree headerSymlinkTree : headerSymlinkTrees) {
            allIncludes.add(CxxSymlinkTreeHeaders.from(headerSymlinkTree, CxxPreprocessables.IncludeType.LOCAL));
        }

        CxxPreprocessorInput.Builder builder = CxxPreprocessorInput.builder();
        builder.putAllPreprocessorFlags(preprocessorFlags);

        // headers from #sandbox are put before #private-headers and #headers on purpose
        // this is the only way to control windows behavior
        if (symlinkTree.isPresent()) {
            for (String includeDir : includeDirs) {
                builder.addIncludes(CxxSandboxInclude.from(symlinkTree.get(), includeDir,
                        CxxPreprocessables.IncludeType.LOCAL));
            }
        }

        builder.addAllIncludes(allIncludes.build()).addAllFrameworks(frameworks);

        CxxPreprocessorInput localPreprocessorInput = builder.build();

        return ImmutableList.<CxxPreprocessorInput>builder().add(localPreprocessorInput)
                .addAll(cxxPreprocessorInputFromDeps).addAll(cxxPreprocessorInputFromTestedRules).build();
    }

    public static BuildTarget createStaticLibraryBuildTarget(BuildTarget target, Flavor platform,
            CxxSourceRuleFactory.PicType pic) {
        return BuildTarget.builder(target).addFlavors(platform)
                .addFlavors(pic == CxxSourceRuleFactory.PicType.PDC ? STATIC_FLAVOR : STATIC_PIC_FLAVOR).build();
    }

    public static BuildTarget createSharedLibraryBuildTarget(BuildTarget target, Flavor platform,
            Linker.LinkType linkType) {
        Flavor linkFlavor;
        switch (linkType) {
        case SHARED:
            linkFlavor = SHARED_FLAVOR;
            break;
        case MACH_O_BUNDLE:
            linkFlavor = MACH_O_BUNDLE_FLAVOR;
            break;
        case EXECUTABLE:
        default:
            throw new IllegalStateException("Only SHARED and MACH_O_BUNDLE types expected, got: " + linkType);
        }
        return BuildTarget.builder(target).addFlavors(platform).addFlavors(linkFlavor).build();
    }

    public static Path getStaticLibraryPath(ProjectFilesystem filesystem, BuildTarget target, Flavor platform,
            CxxSourceRuleFactory.PicType pic, String extension) {
        String name = String.format("lib%s.%s", target.getShortName(), extension);
        return BuildTargets.getGenPath(filesystem, createStaticLibraryBuildTarget(target, platform, pic), "%s")
                .resolve(name);
    }

    public static String getSharedLibrarySoname(Optional<String> declaredSoname, BuildTarget target,
            CxxPlatform platform) {
        if (!declaredSoname.isPresent()) {
            return getDefaultSharedLibrarySoname(target, platform);
        }
        return getNonDefaultSharedLibrarySoname(declaredSoname.get(), platform.getSharedLibraryExtension(),
                platform.getSharedLibraryVersionedExtensionFormat());
    }

    @VisibleForTesting
    static String getNonDefaultSharedLibrarySoname(String declared, String sharedLibraryExtension,
            String sharedLibraryVersionedExtensionFormat) {
        Matcher match = SONAME_EXT_MACRO_PATTERN.matcher(declared);
        if (!match.find()) {
            return declared;
        }
        String version = match.group(1);
        if (version == null) {
            return match.replaceFirst(sharedLibraryExtension);
        }
        return match.replaceFirst(String.format(sharedLibraryVersionedExtensionFormat, version));
    }

    public static String getDefaultSharedLibrarySoname(BuildTarget target, CxxPlatform platform) {
        String libName = Joiner.on('_')
                .join(ImmutableList.builder()
                        .addAll(StreamSupport.stream(target.getBasePath().spliterator(), false)
                                .map(Object::toString).filter(x -> !x.isEmpty()).iterator())
                        .add(target.getShortName()).build());
        String extension = platform.getSharedLibraryExtension();
        return String.format("lib%s.%s", libName, extension);
    }

    public static Path getSharedLibraryPath(ProjectFilesystem filesystem, BuildTarget sharedLibraryTarget,
            String soname) {
        return BuildTargets.getGenPath(filesystem, sharedLibraryTarget, "%s/" + soname);
    }

    private static Path getBinaryOutputPath(BuildTarget target, ProjectFilesystem filesystem,
            Optional<String> extension) {
        String format = extension.map(ext -> "%s." + ext).orElse("%s");
        return BuildTargets.getGenPath(filesystem, target, format);
    }

    @VisibleForTesting
    protected static BuildTarget createCxxLinkTarget(BuildTarget target,
            Optional<LinkerMapMode> flavoredLinkerMapMode) {
        if (flavoredLinkerMapMode.isPresent()) {
            target = target.withAppendedFlavors(flavoredLinkerMapMode.get().getFlavor());
        }
        return target.withAppendedFlavors(CXX_LINK_BINARY_FLAVOR);
    }

    /**
     * @return a function that transforms the {@link FrameworkPath} to search paths with any embedded
     * macros expanded.
     */
    public static RuleKeyAppendableFunction<FrameworkPath, Path> frameworkPathToSearchPath(
            final CxxPlatform cxxPlatform, final SourcePathResolver resolver) {
        return new RuleKeyAppendableFunction<FrameworkPath, Path>() {
            private RuleKeyAppendableFunction<String, String> translateMacrosFn = CxxFlags
                    .getTranslateMacrosFn(cxxPlatform);

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

            @Override
            public Path apply(FrameworkPath input) {
                Function<FrameworkPath, Path> convertToPath = FrameworkPath
                        .getUnexpandedSearchPathFunction(resolver::getAbsolutePath, Functions.identity());
                String pathAsString = convertToPath.apply(input).toString();
                return Paths.get(translateMacrosFn.apply(pathAsString));
            }
        };
    }

    public static CxxLinkAndCompileRules createBuildRulesForCxxBinaryDescriptionArg(BuildRuleParams params,
            BuildRuleResolver resolver, CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform,
            CxxBinaryDescription.Arg args, Optional<StripStyle> stripStyle,
            Optional<LinkerMapMode> flavoredLinkerMapMode) throws NoSuchBuildTargetException {

        SourcePathResolver sourcePathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
        ImmutableMap<String, CxxSource> srcs = parseCxxSources(params.getBuildTarget(), sourcePathResolver,
                cxxPlatform, args);
        ImmutableMap<Path, SourcePath> headers = parseHeaders(params.getBuildTarget(),
                new SourcePathResolver(new SourcePathRuleFinder(resolver)), Optional.of(cxxPlatform), args);
        return createBuildRulesForCxxBinary(params, resolver, cxxBuckConfig, cxxPlatform, srcs, headers,
                params.getDeps(), stripStyle, flavoredLinkerMapMode,
                args.linkStyle.orElse(Linker.LinkableDepType.STATIC), args.preprocessorFlags,
                args.platformPreprocessorFlags, args.langPreprocessorFlags, args.frameworks, args.libraries,
                args.compilerFlags, args.langCompilerFlags, args.platformCompilerFlags, args.prefixHeader,
                args.precompiledHeader, args.linkerFlags, args.platformLinkerFlags, args.cxxRuntimeType,
                args.includeDirs, Optional.empty());
    }

    public static CxxLinkAndCompileRules createBuildRulesForCxxBinary(BuildRuleParams params,
            BuildRuleResolver resolver, CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform,
            ImmutableMap<String, CxxSource> srcs, ImmutableMap<Path, SourcePath> headers,
            Iterable<? extends BuildRule> deps, Optional<StripStyle> stripStyle,
            Optional<LinkerMapMode> flavoredLinkerMapMode, Linker.LinkableDepType linkStyle,
            ImmutableList<String> preprocessorFlags,
            PatternMatchedCollection<ImmutableList<String>> platformPreprocessorFlags,
            ImmutableMap<CxxSource.Type, ImmutableList<String>> langPreprocessorFlags,
            ImmutableSortedSet<FrameworkPath> frameworks, ImmutableSortedSet<FrameworkPath> libraries,
            ImmutableList<String> compilerFlags,
            ImmutableMap<CxxSource.Type, ImmutableList<String>> langCompilerFlags,
            PatternMatchedCollection<ImmutableList<String>> platformCompilerFlags,
            Optional<SourcePath> prefixHeader, Optional<SourcePath> precompiledHeader,
            ImmutableList<String> linkerFlags, PatternMatchedCollection<ImmutableList<String>> platformLinkerFlags,
            Optional<Linker.CxxRuntimeType> cxxRuntimeType, ImmutableList<String> includeDirs,
            Optional<Boolean> xcodePrivateHeadersSymlinks) throws NoSuchBuildTargetException {
        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
        SourcePathResolver sourcePathResolver = new SourcePathResolver(ruleFinder);
        //    TODO(beefon): should be:
        //    Path linkOutput = getLinkOutputPath(
        //        createCxxLinkTarget(params.getBuildTarget(), flavoredLinkerMapMode),
        //        params.getProjectFilesystem());

        BuildTarget target = params.getBuildTarget();
        if (flavoredLinkerMapMode.isPresent()) {
            target = target.withAppendedFlavors(flavoredLinkerMapMode.get().getFlavor());
        }
        Path linkOutput = getBinaryOutputPath(target, params.getProjectFilesystem(),
                cxxPlatform.getBinaryExtension());
        ImmutableList.Builder<Arg> argsBuilder = ImmutableList.builder();
        CommandTool.Builder executableBuilder = new CommandTool.Builder();

        // Setup the header symlink tree and combine all the preprocessor input from this rule
        // and all dependencies.
        boolean shouldCreatePrivateHeadersSymlinks = xcodePrivateHeadersSymlinks.orElse(true);
        HeaderSymlinkTree headerSymlinkTree = requireHeaderSymlinkTree(params, resolver, sourcePathResolver,
                cxxPlatform, headers, HeaderVisibility.PRIVATE, shouldCreatePrivateHeadersSymlinks);
        Optional<SymlinkTree> sandboxTree = Optional.empty();
        if (cxxBuckConfig.sandboxSources()) {
            sandboxTree = createSandboxTree(params, resolver, cxxPlatform);
        }
        ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput = collectCxxPreprocessorInput(params, cxxPlatform,
                CxxFlags.getLanguageFlags(
                        preprocessorFlags, platformPreprocessorFlags, langPreprocessorFlags, cxxPlatform),
                ImmutableList.of(headerSymlinkTree), frameworks,
                CxxPreprocessables.getTransitiveCxxPreprocessorInput(cxxPlatform,
                        RichStream.from(deps).filter(CxxPreprocessorDep.class::isInstance).toImmutableList()),
                includeDirs, sandboxTree);

        // Generate and add all the build rules to preprocess and compile the source to the
        // resolver and get the `SourcePath`s representing the generated object files.
        ImmutableMap<CxxPreprocessAndCompile, SourcePath> objects = CxxSourceRuleFactory
                .requirePreprocessAndCompileRules(params, resolver, sourcePathResolver, ruleFinder, cxxBuckConfig,
                        cxxPlatform, cxxPreprocessorInput,
                        CxxFlags.getLanguageFlags(compilerFlags, platformCompilerFlags, langCompilerFlags,
                                cxxPlatform),
                        prefixHeader, precompiledHeader, srcs,
                        linkStyle == Linker.LinkableDepType.STATIC ? CxxSourceRuleFactory.PicType.PDC
                                : CxxSourceRuleFactory.PicType.PIC,
                        sandboxTree);

        // Build up the linker flags, which support macro expansion.
        ImmutableList<String> resolvedLinkerFlags = CxxFlags.getFlags(linkerFlags, platformLinkerFlags,
                cxxPlatform);
        argsBuilder.addAll(resolvedLinkerFlags.stream().map(MacroArg.toMacroArgFunction(MACRO_HANDLER,
                params.getBuildTarget(), params.getCellRoots(), resolver)::apply).iterator());

        // Special handling for dynamically linked binaries.
        if (linkStyle == Linker.LinkableDepType.SHARED) {

            // Create a symlink tree with for all shared libraries needed by this binary.
            SymlinkTree sharedLibraries = requireSharedLibrarySymlinkTree(params, resolver, sourcePathResolver,
                    cxxPlatform, deps, NativeLinkable.class::isInstance);

            // Embed a origin-relative library path into the binary so it can find the shared libraries.
            // The shared libraries root is absolute. Also need an absolute path to the linkOutput

            Path absLinkOut = params.getBuildTarget().getCellPath().resolve(linkOutput);

            argsBuilder.addAll(StringArg.from(Linkers.iXlinker("-rpath",
                    String.format("%s/%s", cxxPlatform.getLd().resolve(resolver).origin(),
                            absLinkOut.getParent().relativize(sharedLibraries.getRoot()).toString()))));

            // Add all the shared libraries and the symlink tree as inputs to the tool that represents
            // this binary, so that users can attach the proper deps.
            executableBuilder.addDep(sharedLibraries);
            executableBuilder.addInputs(sharedLibraries.getLinks().values());
        }

        // Add object files into the args.
        ImmutableList<SourcePathArg> objectArgs = SourcePathArg.from(sourcePathResolver, objects.values()).stream()
                .map(input -> {
                    Preconditions.checkArgument(input instanceof SourcePathArg);
                    return (SourcePathArg) input;
                }).collect(MoreCollectors.toImmutableList());
        argsBuilder.addAll(FileListableLinkerInputArg.from(objectArgs));

        BuildTarget linkRuleTarget = createCxxLinkTarget(params.getBuildTarget(), flavoredLinkerMapMode);

        CxxLink cxxLink = createCxxLinkRule(params, resolver, cxxBuckConfig, cxxPlatform,
                RichStream.from(deps).filter(NativeLinkable.class).toImmutableList(), linkStyle, frameworks,
                libraries, cxxRuntimeType, sourcePathResolver, ruleFinder, linkOutput, argsBuilder, linkRuleTarget);

        BuildRule binaryRuleForExecutable;
        Optional<CxxStrip> cxxStrip = Optional.empty();
        if (stripStyle.isPresent()) {
            BuildRuleParams cxxParams = params;
            if (flavoredLinkerMapMode.isPresent()) {
                cxxParams = params.withFlavor(flavoredLinkerMapMode.get().getFlavor());
            }
            CxxStrip stripRule = createCxxStripRule(cxxParams, resolver, stripStyle.get(), sourcePathResolver,
                    cxxLink, cxxPlatform);
            cxxStrip = Optional.of(stripRule);
            binaryRuleForExecutable = stripRule;
        } else {
            binaryRuleForExecutable = cxxLink;
        }

        // Add the output of the link as the lone argument needed to invoke this binary as a tool.
        executableBuilder.addArg(new SourcePathArg(sourcePathResolver,
                new BuildTargetSourcePath(binaryRuleForExecutable.getBuildTarget())));

        return new CxxLinkAndCompileRules(cxxLink, cxxStrip, ImmutableSortedSet.copyOf(objects.keySet()),
                executableBuilder.build());
    }

    private static CxxLink createCxxLinkRule(BuildRuleParams params, BuildRuleResolver resolver,
            CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform, Iterable<? extends NativeLinkable> deps,
            Linker.LinkableDepType linkStyle, ImmutableSortedSet<FrameworkPath> frameworks,
            ImmutableSortedSet<FrameworkPath> libraries, Optional<Linker.CxxRuntimeType> cxxRuntimeType,
            SourcePathResolver sourcePathResolver, SourcePathRuleFinder ruleFinder, Path linkOutput,
            ImmutableList.Builder<Arg> argsBuilder, BuildTarget linkRuleTarget) throws NoSuchBuildTargetException {
        CxxLink cxxLink;
        Optional<BuildRule> existingCxxLinkRule = resolver.getRuleOptional(linkRuleTarget);
        if (existingCxxLinkRule.isPresent()) {
            Preconditions.checkArgument(existingCxxLinkRule.get() instanceof CxxLink);
            cxxLink = (CxxLink) existingCxxLinkRule.get();
        } else {
            // Generate the final link rule.  We use the top-level target as the link rule's
            // target, so that it corresponds to the actual binary we build.
            cxxLink = CxxLinkableEnhancer.createCxxLinkableBuildRule(cxxBuckConfig, cxxPlatform, params, resolver,
                    sourcePathResolver, ruleFinder, linkRuleTarget, Linker.LinkType.EXECUTABLE, Optional.empty(),
                    linkOutput, linkStyle, deps, cxxRuntimeType, Optional.empty(), ImmutableSet.of(),
                    NativeLinkableInput.builder().setArgs(argsBuilder.build()).setFrameworks(frameworks)
                            .setLibraries(libraries).build());
            resolver.addToIndex(cxxLink);
        }
        return cxxLink;
    }

    public static CxxStrip createCxxStripRule(BuildRuleParams params, BuildRuleResolver resolver,
            StripStyle stripStyle, SourcePathResolver sourcePathResolver, BuildRule unstrippedBinaryRule,
            CxxPlatform cxxPlatform) {
        BuildRuleParams stripRuleParams = params.copyWithChanges(
                params.getBuildTarget().withAppendedFlavors(CxxStrip.RULE_FLAVOR, stripStyle.getFlavor()),
                Suppliers.ofInstance(ImmutableSortedSet.of(unstrippedBinaryRule)),
                Suppliers.ofInstance(ImmutableSortedSet.of()));
        Optional<BuildRule> exisitingRule = resolver.getRuleOptional(stripRuleParams.getBuildTarget());
        if (exisitingRule.isPresent()) {
            Preconditions.checkArgument(exisitingRule.get() instanceof CxxStrip);
            return (CxxStrip) exisitingRule.get();
        } else {
            CxxStrip cxxStrip = new CxxStrip(stripRuleParams, sourcePathResolver, stripStyle,
                    new BuildTargetSourcePath(unstrippedBinaryRule.getBuildTarget()), cxxPlatform.getStrip(),
                    CxxDescriptionEnhancer.getBinaryOutputPath(stripRuleParams.getBuildTarget(),
                            params.getProjectFilesystem(), cxxPlatform.getBinaryExtension()));
            resolver.addToIndex(cxxStrip);
            return cxxStrip;
        }
    }

    /**
     * Create all build rules needed to generate the compilation database.
     *
     * @return the {@link CxxCompilationDatabase} rule representing the actual compilation database.
     */
    public static CxxCompilationDatabase createCompilationDatabase(BuildRuleParams params,
            BuildRuleResolver ruleResolver, SourcePathResolver pathResolver, SourcePathRuleFinder ruleFinder,
            CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform, CxxConstructorArg arg)
            throws NoSuchBuildTargetException {
        BuildRuleParams paramsWithoutFlavor = params.withoutFlavor(CxxCompilationDatabase.COMPILATION_DATABASE);
        ImmutableMap<CxxPreprocessAndCompile, SourcePath> objects = requireObjects(paramsWithoutFlavor,
                ruleResolver, pathResolver, ruleFinder, cxxBuckConfig, cxxPlatform,
                CxxSourceRuleFactory.PicType.PIC, arg);
        return CxxCompilationDatabase.createCompilationDatabase(params, pathResolver, objects.keySet());
    }

    public static BuildRule createUberCompilationDatabase(BuildRuleParams params, BuildRuleResolver ruleResolver)
            throws NoSuchBuildTargetException {
        Optional<CxxCompilationDatabaseDependencies> compilationDatabases = ruleResolver.requireMetadata(
                params.withoutFlavor(CxxCompilationDatabase.UBER_COMPILATION_DATABASE)
                        .withFlavor(CxxCompilationDatabase.COMPILATION_DATABASE).getBuildTarget(),
                CxxCompilationDatabaseDependencies.class);
        Preconditions.checkState(compilationDatabases.isPresent());
        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver);
        SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
        return new JsonConcatenate(
                params.copyWithDeps(
                        Suppliers.ofInstance(ImmutableSortedSet.copyOf(
                                ruleFinder.filterBuildRuleInputs(compilationDatabases.get().getSourcePaths()))),
                        Suppliers.ofInstance(ImmutableSortedSet.of())),
                pathResolver, pathResolver.getAllAbsolutePaths(compilationDatabases.get().getSourcePaths()),
                "compilation-database-concatenate", "Concatenate compilation databases",
                "uber-compilation-database", "compile_commands.json");
    }

    public static Optional<CxxCompilationDatabaseDependencies> createCompilationDatabaseDependencies(
            BuildTarget buildTarget, FlavorDomain<CxxPlatform> platforms, BuildRuleResolver resolver,
            CxxConstructorArg args) throws NoSuchBuildTargetException {
        Preconditions.checkState(buildTarget.getFlavors().contains(CxxCompilationDatabase.COMPILATION_DATABASE));
        Optional<Flavor> cxxPlatformFlavor = platforms.getFlavor(buildTarget);
        Preconditions.checkState(cxxPlatformFlavor.isPresent(), "Could not find cxx platform in:\n%s",
                Joiner.on(", ").join(buildTarget.getFlavors()));
        ImmutableSet.Builder<SourcePath> sourcePaths = ImmutableSet.builder();
        for (BuildTarget dep : args.deps) {
            Optional<CxxCompilationDatabaseDependencies> compilationDatabases = resolver
                    .requireMetadata(
                            BuildTarget.builder(dep).addFlavors(CxxCompilationDatabase.COMPILATION_DATABASE)
                                    .addFlavors(cxxPlatformFlavor.get()).build(),
                            CxxCompilationDatabaseDependencies.class);
            if (compilationDatabases.isPresent()) {
                sourcePaths.addAll(compilationDatabases.get().getSourcePaths());
            }
        }
        // Not all parts of Buck use require yet, so require the rule here so it's available in the
        // resolver for the parts that don't.
        resolver.requireRule(buildTarget);
        sourcePaths.add(new BuildTargetSourcePath(buildTarget));
        return Optional.of(CxxCompilationDatabaseDependencies.of(sourcePaths.build()));
    }

    public static ImmutableMap<CxxPreprocessAndCompile, SourcePath> requireObjects(BuildRuleParams params,
            BuildRuleResolver ruleResolver, SourcePathResolver sourcePathResolver, SourcePathRuleFinder ruleFinder,
            CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform, CxxSourceRuleFactory.PicType pic,
            CxxConstructorArg args) throws NoSuchBuildTargetException {
        ImmutableMultimap<CxxSource.Type, String> exportedPreprocessorFlags;
        ImmutableMap<Path, SourcePath> exportedHeaders;
        boolean shouldCreatePrivateHeadersSymlinks = true;
        boolean shouldCreatePublicHeadersSymlinks = true;
        if (args instanceof CxxLibraryDescription.Arg) {
            CxxLibraryDescription.Arg hasExportedArgs = (CxxLibraryDescription.Arg) args;
            exportedPreprocessorFlags = CxxFlags.getLanguageFlags(hasExportedArgs.exportedPreprocessorFlags,
                    hasExportedArgs.exportedPlatformPreprocessorFlags,
                    hasExportedArgs.exportedLangPreprocessorFlags, cxxPlatform);
            exportedHeaders = CxxDescriptionEnhancer.parseExportedHeaders(params.getBuildTarget(),
                    sourcePathResolver, Optional.of(cxxPlatform), hasExportedArgs);
            shouldCreatePrivateHeadersSymlinks = hasExportedArgs.xcodePrivateHeadersSymlinks.orElse(true);
            shouldCreatePublicHeadersSymlinks = hasExportedArgs.xcodePublicHeadersSymlinks.orElse(true);
        } else {
            exportedPreprocessorFlags = ImmutableMultimap.of();
            exportedHeaders = ImmutableMap.of();
        }

        HeaderSymlinkTree headerSymlinkTree = CxxDescriptionEnhancer.requireHeaderSymlinkTree(params, ruleResolver,
                sourcePathResolver, cxxPlatform,
                CxxDescriptionEnhancer.parseHeaders(params.getBuildTarget(), sourcePathResolver,
                        Optional.of(cxxPlatform), args),
                HeaderVisibility.PRIVATE, shouldCreatePrivateHeadersSymlinks);

        Optional<SymlinkTree> sandboxTree = Optional.empty();
        if (cxxBuckConfig.sandboxSources()) {
            sandboxTree = createSandboxTree(params, ruleResolver, cxxPlatform);
        }

        ImmutableList<CxxPreprocessorInput> cxxPreprocessorInputFromDependencies = CxxDescriptionEnhancer
                .collectCxxPreprocessorInput(params, cxxPlatform,
                        CxxFlags.getLanguageFlags(args.preprocessorFlags, args.platformPreprocessorFlags,
                                args.langPreprocessorFlags, cxxPlatform),
                        ImmutableList.of(headerSymlinkTree), ImmutableSet.of(),
                        CxxLibraryDescription.getTransitiveCxxPreprocessorInput(params, ruleResolver,
                                sourcePathResolver, cxxPlatform, exportedPreprocessorFlags, exportedHeaders,
                                args.frameworks, shouldCreatePublicHeadersSymlinks),
                        args.includeDirs, sandboxTree);

        // Create rule to build the object files.
        return CxxSourceRuleFactory.requirePreprocessAndCompileRules(params, ruleResolver, sourcePathResolver,
                ruleFinder, cxxBuckConfig, cxxPlatform, cxxPreprocessorInputFromDependencies,
                CxxFlags.getLanguageFlags(args.compilerFlags, args.platformCompilerFlags, args.langCompilerFlags,
                        cxxPlatform),
                args.prefixHeader, args.precompiledHeader, CxxDescriptionEnhancer.parseCxxSources(
                        params.getBuildTarget(), sourcePathResolver, cxxPlatform, args),
                pic, sandboxTree);
    }

    public static Optional<SymlinkTree> createSandboxTree(BuildRuleParams params, BuildRuleResolver ruleResolver,
            CxxPlatform cxxPlatform) throws NoSuchBuildTargetException {
        return Optional.of(requireSandboxSymlinkTree(params, ruleResolver, cxxPlatform));
    }

    /**
     * @return the {@link BuildTarget} to use for the {@link BuildRule} generating the
     *    symlink tree of shared libraries.
     */
    public static BuildTarget createSharedLibrarySymlinkTreeTarget(BuildTarget target, Flavor platform) {
        return BuildTarget.builder(target).addFlavors(SHARED_LIBRARY_SYMLINK_TREE_FLAVOR).addFlavors(platform)
                .build();
    }

    /**
     * @return the {@link Path} to use for the symlink tree of headers.
     */
    public static Path getSharedLibrarySymlinkTreePath(ProjectFilesystem filesystem, BuildTarget target,
            Flavor platform) {
        return BuildTargets.getGenPath(filesystem, createSharedLibrarySymlinkTreeTarget(target, platform), "%s");
    }

    /**
     * Build a {@link HeaderSymlinkTree} of all the shared libraries found via the top-level rule's
     * transitive dependencies.
     */
    public static SymlinkTree createSharedLibrarySymlinkTree(BuildRuleParams params,
            SourcePathResolver pathResolver, CxxPlatform cxxPlatform, Iterable<? extends BuildRule> deps,
            Predicate<Object> traverse, Predicate<Object> skip) throws NoSuchBuildTargetException {

        BuildTarget symlinkTreeTarget = createSharedLibrarySymlinkTreeTarget(params.getBuildTarget(),
                cxxPlatform.getFlavor());
        Path symlinkTreeRoot = getSharedLibrarySymlinkTreePath(params.getProjectFilesystem(),
                params.getBuildTarget(), cxxPlatform.getFlavor());

        ImmutableSortedMap<String, SourcePath> libraries = NativeLinkables.getTransitiveSharedLibraries(cxxPlatform,
                deps, traverse, skip);

        ImmutableMap.Builder<Path, SourcePath> links = ImmutableMap.builder();
        for (Map.Entry<String, SourcePath> ent : libraries.entrySet()) {
            links.put(Paths.get(ent.getKey()), ent.getValue());
        }
        return new SymlinkTree(
                params.copyWithChanges(symlinkTreeTarget, Suppliers.ofInstance(ImmutableSortedSet.of()),
                        Suppliers.ofInstance(ImmutableSortedSet.of())),
                pathResolver, symlinkTreeRoot, links.build());
    }

    public static SymlinkTree createSharedLibrarySymlinkTree(BuildRuleParams params,
            SourcePathResolver pathResolver, CxxPlatform cxxPlatform, Iterable<? extends BuildRule> deps,
            Predicate<Object> traverse) throws NoSuchBuildTargetException {
        return createSharedLibrarySymlinkTree(params, pathResolver, cxxPlatform, deps, traverse, x -> false);
    }

    public static SymlinkTree requireSharedLibrarySymlinkTree(BuildRuleParams params, BuildRuleResolver resolver,
            SourcePathResolver pathResolver, CxxPlatform cxxPlatform, Iterable<? extends BuildRule> deps,
            Predicate<Object> traverse) throws NoSuchBuildTargetException {
        BuildTarget target = createSharedLibrarySymlinkTreeTarget(params.getBuildTarget(), cxxPlatform.getFlavor());
        SymlinkTree tree = resolver.getRuleOptionalWithType(target, SymlinkTree.class).orElse(null);
        if (tree == null) {
            tree = resolver
                    .addToIndex(createSharedLibrarySymlinkTree(params, pathResolver, cxxPlatform, deps, traverse));
        }
        return tree;
    }

    public static Flavor flavorForLinkableDepType(Linker.LinkableDepType linkableDepType) {
        switch (linkableDepType) {
        case STATIC:
            return STATIC_FLAVOR;
        case STATIC_PIC:
            return STATIC_PIC_FLAVOR;
        case SHARED:
            return SHARED_FLAVOR;
        }
        throw new RuntimeException(String.format("Unsupported LinkableDepType: '%s'", linkableDepType));
    }

    public static SymlinkTree createSandboxTreeBuildRule(BuildRuleResolver resolver, CxxConstructorArg args,
            CxxPlatform platform, BuildRuleParams params) {
        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
        SourcePathResolver sourcePathResolver = new SourcePathResolver(ruleFinder);
        ImmutableCollection<SourcePath> privateHeaders = parseHeaders(params.getBuildTarget(), sourcePathResolver,
                Optional.of(platform), args).values();
        ImmutableCollection<CxxSource> sources = parseCxxSources(params.getBuildTarget(), sourcePathResolver,
                platform, args).values();
        HashMap<Path, SourcePath> links = new HashMap<>();
        for (SourcePath headerPath : privateHeaders) {
            links.put(Paths.get(sourcePathResolver.getSourcePathName(params.getBuildTarget(), headerPath)),
                    headerPath);
        }
        if (args instanceof CxxLibraryDescription.Arg) {
            ImmutableCollection<SourcePath> publicHeaders = CxxDescriptionEnhancer
                    .parseExportedHeaders(params.getBuildTarget(), sourcePathResolver, Optional.of(platform),
                            (CxxLibraryDescription.Arg) args)
                    .values();
            for (SourcePath headerPath : publicHeaders) {
                links.put(Paths.get(sourcePathResolver.getSourcePathName(params.getBuildTarget(), headerPath)),
                        headerPath);
            }
        }
        for (CxxSource source : sources) {
            SourcePath sourcePath = source.getPath();
            links.put(Paths.get(sourcePathResolver.getSourcePathName(params.getBuildTarget(), sourcePath)),
                    sourcePath);
        }
        return createSandboxSymlinkTree(params, sourcePathResolver, platform, ImmutableMap.copyOf(links));
    }

    /**
     * Resolve the map of names to SourcePaths to a map of names to CxxSource objects.
     */
    private static ImmutableMap<String, CxxSource> resolveCxxSources(
            ImmutableMap<String, SourceWithFlags> sources) {

        ImmutableMap.Builder<String, CxxSource> cxxSources = ImmutableMap.builder();

        // For each entry in the input C/C++ source, build a CxxSource object to wrap
        // it's name, input path, and output object file path.
        for (ImmutableMap.Entry<String, SourceWithFlags> ent : sources.entrySet()) {
            String extension = Files.getFileExtension(ent.getKey());
            Optional<CxxSource.Type> type = CxxSource.Type.fromExtension(extension);
            if (!type.isPresent()) {
                throw new HumanReadableException("invalid extension \"%s\": %s", extension, ent.getKey());
            }
            cxxSources.put(ent.getKey(),
                    CxxSource.of(type.get(), ent.getValue().getSourcePath(), ent.getValue().getFlags()));
        }

        return cxxSources.build();
    }
}