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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2016-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.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.Flavored;
import com.facebook.buck.model.MacroException;
import com.facebook.buck.model.MacroFinder;
import com.facebook.buck.model.MacroReplacer;
import com.facebook.buck.parser.BuildTargetParseException;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.parser.BuildTargetPatternParser;
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.CellPathResolver;
import com.facebook.buck.rules.RuleKeyAppendable;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SymlinkTree;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.args.StringArg;
import com.facebook.buck.rules.macros.AbstractMacroExpander;
import com.facebook.buck.rules.macros.ExecutableMacroExpander;
import com.facebook.buck.rules.macros.LocationMacroExpander;
import com.facebook.buck.rules.macros.MacroExpander;
import com.facebook.buck.rules.macros.MacroHandler;
import com.facebook.buck.rules.macros.StringExpander;
import com.facebook.buck.shell.AbstractGenruleDescription;
import com.facebook.buck.util.Escaper;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.versions.TargetNodeTranslator;
import com.facebook.buck.versions.TargetTranslatorOverridingDescription;
import com.facebook.buck.versions.VersionPropagator;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;

import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

public class CxxGenruleDescription extends AbstractGenruleDescription<AbstractGenruleDescription.Arg>
        implements Flavored, VersionPropagator<AbstractGenruleDescription.Arg>,
        TargetTranslatorOverridingDescription<AbstractGenruleDescription.Arg> {

    private static final MacroFinder MACRO_FINDER = new MacroFinder();

    private final FlavorDomain<CxxPlatform> cxxPlatforms;

    public CxxGenruleDescription(FlavorDomain<CxxPlatform> cxxPlatforms) {
        this.cxxPlatforms = cxxPlatforms;
    }

    /**
     * @return a new {@link BuildTargetSourcePath} for an existing {@link BuildTargetSourcePath} which
     *         refers to a {@link CxxGenrule} with the given {@code platform} flavor applied.
     */
    public static SourcePath fixupSourcePath(BuildRuleResolver ruleResolver, SourcePathRuleFinder ruleFinder,
            CxxPlatform platform, SourcePath path) throws NoSuchBuildTargetException {
        Optional<BuildRule> rule = ruleFinder.getRule(path);
        if (rule.isPresent() && rule.get() instanceof CxxGenrule) {
            BuildRule platformRule = ruleResolver
                    .requireRule(rule.get().getBuildTarget().withAppendedFlavors(platform.getFlavor()));
            path = new BuildTargetSourcePath(platformRule.getBuildTarget());
        }
        return path;
    }

    public static ImmutableList<SourcePath> fixupSourcePaths(BuildRuleResolver ruleResolver,
            SourcePathRuleFinder ruleFinder, CxxPlatform cxxPlatform, ImmutableList<SourcePath> paths)
            throws NoSuchBuildTargetException {
        ImmutableList.Builder<SourcePath> fixed = ImmutableList.builder();
        for (SourcePath path : paths) {
            fixed.add(fixupSourcePath(ruleResolver, ruleFinder, cxxPlatform, path));
        }
        return fixed.build();
    }

    public static <T> ImmutableMap<T, SourcePath> fixupSourcePaths(BuildRuleResolver ruleResolver,
            SourcePathRuleFinder ruleFinder, CxxPlatform cxxPlatform, ImmutableMap<T, SourcePath> paths)
            throws NoSuchBuildTargetException {
        ImmutableMap.Builder<T, SourcePath> fixed = ImmutableMap.builder();
        for (Map.Entry<T, SourcePath> ent : paths.entrySet()) {
            fixed.put(ent.getKey(), fixupSourcePath(ruleResolver, ruleFinder, cxxPlatform, ent.getValue()));
        }
        return fixed.build();
    }

    private static String shquoteJoin(Iterable<String> args) {
        return Joiner.on(' ').join(Iterables.transform(args, Escaper.SHELL_ESCAPER));
    }

    @Override
    public Arg createUnpopulatedConstructorArg() {
        return new Arg();
    }

    @Override
    public boolean hasFlavors(ImmutableSet<Flavor> flavors) {
        return cxxPlatforms.containsAnyOf(flavors);
    }

    @Override
    protected MacroHandler getMacroHandlerForParseTimeDeps() {
        ImmutableMap.Builder<String, MacroExpander> macros = ImmutableMap.builder();
        macros.put("exe", new ExecutableMacroExpander());
        macros.put("location", new LocationMacroExpander());
        macros.put("location-platform", new LocationMacroExpander());
        macros.put("platform-name", new StringExpander(""));
        macros.put("cc", new CxxPlatformParseTimeDepsExpander(cxxPlatforms));
        macros.put("cxx", new CxxPlatformParseTimeDepsExpander(cxxPlatforms));
        macros.put("cflags", new StringExpander(""));
        macros.put("cxxflags", new StringExpander(""));
        macros.put("cppflags", new ParseTimeDepsExpander(Filter.NONE));
        macros.put("cxxppflags", new ParseTimeDepsExpander(Filter.NONE));
        macros.put("solibs", new ParseTimeDepsExpander(Filter.NONE));
        macros.put("ld", new CxxPlatformParseTimeDepsExpander(cxxPlatforms));
        for (Linker.LinkableDepType style : Linker.LinkableDepType.values()) {
            for (Filter filter : Filter.values()) {
                macros.put(String.format("ldflags-%s%s",
                        CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, style.toString()),
                        filter == Filter.PARAM ? "-filter" : ""), new ParseTimeDepsExpander(filter));
            }
        }
        return new MacroHandler(macros.build());
    }

    @Override
    protected <A extends Arg> MacroHandler getMacroHandler(BuildRuleParams params, BuildRuleResolver resolver,
            TargetGraph targetGraph, A args) {
        final Optional<CxxPlatform> cxxPlatform = cxxPlatforms.getValue(params.getBuildTarget());
        Preconditions.checkState(cxxPlatform.isPresent());
        ImmutableMap.Builder<String, MacroExpander> macros = ImmutableMap.builder();
        macros.put("exe", new ExecutableMacroExpander());
        macros.put("location", new LocationMacroExpander());
        macros.put("platform-name", new StringExpander(cxxPlatform.get().getFlavor().toString()));
        macros.put("location-platform", new LocationMacroExpander() {
            @Override
            protected BuildRule resolve(BuildRuleResolver resolver, BuildTarget input) throws MacroException {
                try {
                    return resolver.requireRule(input.withAppendedFlavors(cxxPlatform.get().getFlavor()));
                } catch (NoSuchBuildTargetException e) {
                    throw new MacroException(e.getHumanReadableErrorMessage());
                }
            }
        });
        macros.put("cc", new ToolExpander(cxxPlatform.get().getCc().resolve(resolver)));
        macros.put("cxx", new ToolExpander(cxxPlatform.get().getCxx().resolve(resolver)));

        ImmutableList<String> asflags = cxxPlatform.get().getAsflags();
        ImmutableList<String> cflags = cxxPlatform.get().getCflags();
        ImmutableList<String> cxxflags = cxxPlatform.get().getCxxflags();
        macros.put("cflags", new StringExpander(shquoteJoin(Iterables.concat(cflags, asflags))));
        macros.put("cxxflags", new StringExpander(shquoteJoin(Iterables.concat(cxxflags, asflags))));

        macros.put("cppflags", new CxxPreprocessorFlagsExpander(cxxPlatform.get(), CxxSource.Type.C));
        macros.put("cxxppflags", new CxxPreprocessorFlagsExpander(cxxPlatform.get(), CxxSource.Type.CXX));
        macros.put("ld", new ToolExpander(cxxPlatform.get().getLd().resolve(resolver)));
        for (Linker.LinkableDepType depType : Linker.LinkableDepType.values()) {
            for (Filter filter : Filter.values()) {
                macros.put(
                        String.format("ldflags-%s%s", depType.toString().toLowerCase().replace('_', '-'),
                                filter == Filter.PARAM ? "-filter" : ""),
                        new CxxLinkerFlagsExpander(params, cxxPlatform.get(), depType, args.out, filter));
            }
        }
        return new MacroHandler(macros.build());
    }

    @Override
    public <A extends Arg> BuildRule createBuildRule(TargetGraph targetGraph, BuildRuleParams params,
            BuildRuleResolver resolver, A args) throws NoSuchBuildTargetException {
        Optional<CxxPlatform> cxxPlatform = cxxPlatforms.getValue(params.getBuildTarget());
        if (cxxPlatform.isPresent()) {
            return super.createBuildRule(targetGraph,
                    params.copyWithBuildTarget(
                            params.getBuildTarget().withAppendedFlavors(cxxPlatform.get().getFlavor())),
                    resolver, args);
        }
        SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
        return new CxxGenrule(params, pathResolver, resolver, args.out);
    }

    @Override
    public Iterable<BuildTarget> findDepsForTargetFromConstructorArgs(BuildTarget buildTarget,
            CellPathResolver cellRoots, Arg constructorArg) {
        ImmutableSet.Builder<BuildTarget> targets = ImmutableSet.builder();

        // Add in all parse time deps from the C/C++ platforms.
        for (CxxPlatform cxxPlatform : cxxPlatforms.getValues()) {
            targets.addAll(CxxPlatforms.getParseTimeDeps(cxxPlatform));
        }

        // Add in parse time deps from parent.
        targets.addAll(super.findDepsForTargetFromConstructorArgs(buildTarget, cellRoots, constructorArg));

        return targets.build();
    }

    private ImmutableMap<String, MacroReplacer> getMacroReplacersForTargetTranslation(BuildTarget target,
            CellPathResolver cellNames, TargetNodeTranslator translator) {
        BuildTargetPatternParser<?> buildTargetPatternParser = BuildTargetPatternParser
                .forBaseName(target.getBaseName());

        ImmutableMap.Builder<String, MacroReplacer> macros = ImmutableMap.builder();

        ImmutableList.of("exe", "location", "location-platform", "cppflags", "cxxppflags", "solibs")
                .forEach(name -> macros.put(name, new TargetTranslatorMacroReplacer(new AsIsMacroReplacer(name),
                        Filter.NONE, buildTargetPatternParser, cellNames, translator)));

        ImmutableList.of("platform-name", "cc", "cflags", "cxx", "cxxflags", "ld")
                .forEach(name -> macros.put(name, new AsIsMacroReplacer(name)));

        for (Linker.LinkableDepType style : Linker.LinkableDepType.values()) {
            for (Filter filter : Filter.values()) {
                String name = String.format("ldflags-%s%s",
                        CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, style.toString()),
                        filter == Filter.PARAM ? "-filter" : "");
                macros.put(name, new TargetTranslatorMacroReplacer(new AsIsMacroReplacer(name), filter,
                        buildTargetPatternParser, cellNames, translator));
            }
        }
        return macros.build();
    }

    private String translateCmd(BuildTarget root, CellPathResolver cellNames, TargetNodeTranslator translator,
            String field, String cmd) {
        try {
            return MACRO_FINDER.replace(getMacroReplacersForTargetTranslation(root, cellNames, translator), cmd,
                    false);
        } catch (MacroException e) {
            throw new HumanReadableException(e, "%s: \"%s\": error expanding macros: %s", root, field,
                    e.getMessage());
        }
    }

    @Override
    public Optional<Arg> translateConstructorArg(BuildTarget target, CellPathResolver cellNames,
            TargetNodeTranslator translator, Arg constructorArg) {
        Arg newConstructorArg = createUnpopulatedConstructorArg();
        translator.translateConstructorArg(constructorArg, newConstructorArg);
        newConstructorArg.cmd = newConstructorArg.cmd
                .map(c -> translateCmd(target, cellNames, translator, "cmd", c));
        newConstructorArg.bash = newConstructorArg.bash
                .map(c -> translateCmd(target, cellNames, translator, "bash", c));
        newConstructorArg.cmdExe = newConstructorArg.cmdExe
                .map(c -> translateCmd(target, cellNames, translator, "cmd_exe", c));
        return Optional.of(newConstructorArg);
    }

    /**
     * A build target macro expander just used at parse time to extract deps from the preprocessor
     * flag macros.
     */
    private static class ParseTimeDepsExpander extends FilterAndTargetsExpander {

        public ParseTimeDepsExpander(Filter filter) {
            super(filter);
        }

        @Override
        protected String expand(BuildRuleResolver resolver, ImmutableList<BuildRule> rule, Optional<Pattern> filter)
                throws MacroException {
            throw new IllegalStateException();
        }

    }

    /**
     * A macro expander that expands to a specific {@link Tool}.
     */
    private static class ToolExpander implements MacroExpander {

        private final Tool tool;

        public ToolExpander(Tool tool) {
            this.tool = tool;
        }

        @Override
        public String expand(BuildTarget target, CellPathResolver cellNames, BuildRuleResolver resolver,
                ImmutableList<String> input) throws MacroException {
            SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
            return shquoteJoin(tool.getCommandPrefix(pathResolver));
        }

        @Override
        public ImmutableList<BuildRule> extractBuildTimeDeps(BuildTarget target, CellPathResolver cellNames,
                BuildRuleResolver resolver, ImmutableList<String> input) throws MacroException {
            SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
            return ImmutableList.copyOf(tool.getDeps(ruleFinder));
        }

        @Override
        public ImmutableList<BuildTarget> extractParseTimeDeps(BuildTarget target, CellPathResolver cellNames,
                ImmutableList<String> input) throws MacroException {
            // We already return all platform-specific parse-time deps from
            // `findDepsForTargetFromConstructorArgs`.
            return ImmutableList.of();
        }

        @Override
        public Object extractRuleKeyAppendables(BuildTarget target, CellPathResolver cellNames,
                BuildRuleResolver resolver, ImmutableList<String> input) throws MacroException {
            return tool;
        }

    }

    private abstract static class FilterAndTargetsExpander extends AbstractMacroExpander<FilterAndTargets> {

        private final Filter filter;

        public FilterAndTargetsExpander(Filter filter) {
            this.filter = filter;
        }

        @Override
        protected final FilterAndTargets parse(BuildTarget target, CellPathResolver cellNames,
                ImmutableList<String> input) throws MacroException {

            if (this.filter == Filter.PARAM && input.size() < 1) {
                throw new MacroException("expected at least 1 argument");
            }

            Iterator<String> itr = input.iterator();

            Optional<Pattern> filter = this.filter == Filter.PARAM ? Optional.of(Pattern.compile(itr.next()))
                    : Optional.empty();

            ImmutableList.Builder<BuildTarget> targets = ImmutableList.builder();
            while (itr.hasNext()) {
                targets.add(BuildTargetParser.INSTANCE.parse(itr.next(),
                        BuildTargetPatternParser.forBaseName(target.getBaseName()), cellNames));
            }

            return new FilterAndTargets(filter, targets.build());
        }

        protected ImmutableList<BuildRule> resolve(BuildRuleResolver resolver, ImmutableList<BuildTarget> input)
                throws MacroException {
            ImmutableList.Builder<BuildRule> rules = ImmutableList.builder();
            for (BuildTarget ruleTarget : input) {
                Optional<BuildRule> rule = resolver.getRuleOptional(ruleTarget);
                if (!rule.isPresent()) {
                    throw new MacroException(String.format("no rule %s", ruleTarget));
                }
                rules.add(rule.get());
            }
            return rules.build();
        }

        protected abstract String expand(BuildRuleResolver resolver, ImmutableList<BuildRule> rules,
                Optional<Pattern> filter) throws MacroException;

        @Override
        public String expandFrom(BuildTarget target, CellPathResolver cellNames, BuildRuleResolver resolver,
                FilterAndTargets input) throws MacroException {
            return expand(resolver, resolve(resolver, input.targets), input.filter);
        }

        protected ImmutableList<BuildRule> extractBuildTimeDeps(
                @SuppressWarnings("unused") BuildRuleResolver resolver, ImmutableList<BuildRule> rules,
                @SuppressWarnings("unused") Optional<Pattern> filter) throws MacroException {
            return rules;
        }

        @Override
        public ImmutableList<BuildRule> extractBuildTimeDepsFrom(BuildTarget target, CellPathResolver cellNames,
                BuildRuleResolver resolver, FilterAndTargets input) throws MacroException {
            return extractBuildTimeDeps(resolver, resolve(resolver, input.targets), input.filter);
        }

        @Override
        public ImmutableList<BuildTarget> extractParseTimeDepsFrom(BuildTarget target, CellPathResolver cellNames,
                FilterAndTargets input) {
            return input.targets;
        }

        @Override
        public Object extractRuleKeyAppendablesFrom(BuildTarget target, CellPathResolver cellNames,
                BuildRuleResolver resolver, FilterAndTargets input) throws MacroException {
            return input.targets.stream().map(BuildTargetSourcePath::new)
                    .collect(MoreCollectors.toImmutableSortedSet(Ordering.natural()));
        }

    }

    /**
     * A build target expander that replaces lists of build target with their transitive preprocessor
     * input.
     */
    private static class CxxPreprocessorFlagsExpander extends FilterAndTargetsExpander {

        private final CxxPlatform cxxPlatform;
        private final CxxSource.Type sourceType;

        public CxxPreprocessorFlagsExpander(CxxPlatform cxxPlatform, CxxSource.Type sourceType) {
            super(Filter.NONE);
            this.cxxPlatform = cxxPlatform;
            this.sourceType = sourceType;
        }

        /**
         * Make sure all resolved targets are instances of {@link CxxPreprocessorDep}.
         */
        @Override
        protected ImmutableList<BuildRule> resolve(BuildRuleResolver resolver, ImmutableList<BuildTarget> input)
                throws MacroException {
            return FluentIterable.from(super.resolve(resolver, input)).filter(CxxPreprocessorDep.class::isInstance)
                    .toList();
        }

        /**
         * Get the transitive C/C++ preprocessor input rooted at the given rules.
         */
        private Collection<CxxPreprocessorInput> getCxxPreprocessorInput(ImmutableList<BuildRule> rules)
                throws MacroException {
            try {
                return CxxPreprocessables.getTransitiveCxxPreprocessorInput(cxxPlatform, rules);
            } catch (NoSuchBuildTargetException e) {
                throw new MacroException(String.format("failed getting preprocessor input: %s", e.getMessage()), e);
            }
        }

        /**
         * Return the {@link PreprocessorFlags} object formed by the transitive C/C++ preprocessor
         * input for the given rules.
         */
        private PreprocessorFlags getPreprocessorFlags(Iterable<CxxPreprocessorInput> transitivePreprocessorInput) {
            PreprocessorFlags.Builder ppFlagsBuilder = PreprocessorFlags.builder();
            ExplicitCxxToolFlags.Builder toolFlagsBuilder = CxxToolFlags.explicitBuilder();
            toolFlagsBuilder.setPlatformFlags(CxxSourceTypes.getPlatformPreprocessFlags(cxxPlatform, sourceType));
            for (CxxPreprocessorInput input : transitivePreprocessorInput) {
                ppFlagsBuilder.addAllIncludes(input.getIncludes());
                ppFlagsBuilder.addAllFrameworkPaths(input.getFrameworks());
                toolFlagsBuilder.addAllRuleFlags(input.getPreprocessorFlags().get(sourceType));
            }
            ppFlagsBuilder.setOtherFlags(toolFlagsBuilder.build());
            return ppFlagsBuilder.build();
        }

        /**
         * Expand the preprocessor input for the given rules into a shell-escaped string containing all
         * flags and header trees.
         */
        @Override
        protected String expand(BuildRuleResolver resolver, ImmutableList<BuildRule> rules,
                Optional<Pattern> filter) throws MacroException {
            SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
            PreprocessorFlags ppFlags = getPreprocessorFlags(getCxxPreprocessorInput(rules));
            Preprocessor preprocessor = CxxSourceTypes.getPreprocessor(cxxPlatform, sourceType).resolve(resolver);
            CxxToolFlags flags = ppFlags.toToolFlags(pathResolver, PathShortener.identity(),
                    CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, pathResolver), preprocessor,
                    /* pch */ Optional.empty());
            return Joiner.on(' ').join(Iterables.transform(flags.getAllFlags(), Escaper.SHELL_ESCAPER));
        }

        @Override
        protected ImmutableList<BuildRule> extractBuildTimeDeps(BuildRuleResolver resolver,
                ImmutableList<BuildRule> rules, Optional<Pattern> filter) throws MacroException {
            SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
            ImmutableList.Builder<BuildRule> deps = ImmutableList.builder();
            for (CxxPreprocessorInput input : getCxxPreprocessorInput(rules)) {
                deps.addAll(input.getDeps(resolver, ruleFinder));
            }
            return deps.build();
        }

        @Override
        public Object extractRuleKeyAppendablesFrom(final BuildTarget target, final CellPathResolver cellNames,
                final BuildRuleResolver resolver, FilterAndTargets input) throws MacroException {
            final Iterable<CxxPreprocessorInput> transitivePreprocessorInput = getCxxPreprocessorInput(
                    resolve(resolver, input.targets));
            final PreprocessorFlags ppFlags = getPreprocessorFlags(transitivePreprocessorInput);
            return (RuleKeyAppendable) sink -> {
                ppFlags.appendToRuleKey(sink, cxxPlatform.getCompilerDebugPathSanitizer());
                sink.setReflectively("headers", FluentIterable.from(transitivePreprocessorInput)
                        .transformAndConcat(CxxPreprocessorInput::getIncludes).toList());
            };
        }

    }

    /**
     * A build target expander that replaces lists of build target with their transitive preprocessor
     * input.
     */
    private static class CxxLinkerFlagsExpander extends FilterAndTargetsExpander {

        private final BuildRuleParams params;
        private final CxxPlatform cxxPlatform;
        private final Linker.LinkableDepType depType;
        private final String out;

        public CxxLinkerFlagsExpander(BuildRuleParams params, CxxPlatform cxxPlatform,
                Linker.LinkableDepType depType, String out, Filter filter) {
            super(filter);
            this.params = params;
            this.cxxPlatform = cxxPlatform;
            this.depType = depType;
            this.out = out;
        }

        /**
         * @return a {@link SymlinkTree} containing all the transitive shared libraries from the given
         *         roots linked in by their library name.
         */
        private SymlinkTree requireSymlinkTree(BuildRuleResolver resolver, ImmutableList<BuildRule> rules)
                throws MacroException {
            SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
            BuildTarget symlinkTreeTarget = CxxDescriptionEnhancer
                    .createSharedLibrarySymlinkTreeTarget(params.getBuildTarget(), cxxPlatform.getFlavor());
            SymlinkTree symlinkTree = resolver.getRuleOptionalWithType(symlinkTreeTarget, SymlinkTree.class)
                    .orElse(null);
            if (symlinkTree == null) {
                try {
                    symlinkTree = resolver.addToIndex(CxxDescriptionEnhancer.createSharedLibrarySymlinkTree(params,
                            pathResolver, cxxPlatform, rules, NativeLinkable.class::isInstance));
                } catch (NoSuchBuildTargetException e) {
                    throw new MacroException(
                            String.format("cannot create shared library symlink tree: %s: %s", e, e.getMessage()),
                            e);
                }
            }
            return symlinkTree;
        }

        /**
         * @return the list of {@link com.facebook.buck.rules.args.Arg} required for dynamic linking so
         *         that linked binaries can find their shared library dependencies at runtime.
         */
        private ImmutableList<com.facebook.buck.rules.args.Arg> getSharedLinkArgs(BuildRuleResolver resolver,
                ImmutableList<BuildRule> rules) throws MacroException {

            // 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 linkOutput = BuildTargets.getGenPath(params.getProjectFilesystem(), params.getBuildTarget(), "%s")
                    .resolve(out);
            Path absLinkOut = params.getBuildTarget().getCellPath().resolve(linkOutput);
            SymlinkTree symlinkTree = requireSymlinkTree(resolver, rules);
            return ImmutableList.copyOf(StringArg.from(Linkers.iXlinker("-rpath",
                    String.format("%s/%s", cxxPlatform.getLd().resolve(resolver).origin(),
                            absLinkOut.getParent().relativize(symlinkTree.getRoot()).toString()))));
        }

        private NativeLinkableInput getNativeLinkableInput(Iterable<BuildRule> rules,
                final Optional<Pattern> filter) throws MacroException {
            try {
                ImmutableMap<BuildTarget, NativeLinkable> nativeLinkables = NativeLinkables.getNativeLinkables(
                        cxxPlatform, FluentIterable.from(rules).filter(NativeLinkable.class), depType,
                        !filter.isPresent() ? x -> true : input -> {
                            Preconditions.checkArgument(input instanceof BuildRule);
                            BuildRule rule = (BuildRule) input;
                            return filter.get()
                                    .matcher(String.format("%s(%s)", rule.getType(), rule.getBuildTarget())).find();
                        });
                ImmutableList.Builder<NativeLinkableInput> nativeLinkableInputs = ImmutableList.builder();
                for (NativeLinkable nativeLinkable : nativeLinkables.values()) {
                    nativeLinkableInputs
                            .add(NativeLinkables.getNativeLinkableInput(cxxPlatform, depType, nativeLinkable));
                }
                return NativeLinkableInput.concat(nativeLinkableInputs.build());
            } catch (NoSuchBuildTargetException e) {
                throw new MacroException(String.format("failed getting native linker args: %s", e.getMessage()), e);
            }
        }

        /**
         * Make sure all resolved targets are instances of {@link NativeLinkable}.
         */
        @Override
        protected ImmutableList<BuildRule> resolve(BuildRuleResolver resolver, ImmutableList<BuildTarget> input)
                throws MacroException {
            return FluentIterable.from(super.resolve(resolver, input)).filter(NativeLinkable.class::isInstance)
                    .toList();
        }

        /**
         * Return the args formed by the transitive native linkable input for the given rules.
         */
        private ImmutableList<com.facebook.buck.rules.args.Arg> getLinkerArgs(BuildRuleResolver resolver,
                ImmutableList<BuildRule> rules, Optional<Pattern> filter) throws MacroException {
            ImmutableList.Builder<com.facebook.buck.rules.args.Arg> args = ImmutableList.builder();
            args.addAll(StringArg.from(cxxPlatform.getLdflags()));
            if (depType == Linker.LinkableDepType.SHARED) {
                args.addAll(getSharedLinkArgs(resolver, rules));
            }
            args.addAll(getNativeLinkableInput(rules, filter).getArgs());
            return args.build();
        }

        /**
         * Expand the native linkable input for the given rules into a shell-escaped string containing
         * all linker flags.
         */
        @Override
        public String expand(BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
                throws MacroException {
            return shquoteJoin(com.facebook.buck.rules.args.Arg.stringify(getLinkerArgs(resolver, rules, filter)));
        }

        @Override
        protected ImmutableList<BuildRule> extractBuildTimeDeps(BuildRuleResolver resolver,
                ImmutableList<BuildRule> rules, Optional<Pattern> filter) throws MacroException {
            SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
            ImmutableList.Builder<BuildRule> deps = ImmutableList.builder();
            deps.addAll(getLinkerArgs(resolver, rules, filter).stream()
                    .flatMap(arg -> arg.getDeps(ruleFinder).stream()).iterator());
            if (depType == Linker.LinkableDepType.SHARED) {
                deps.add(requireSymlinkTree(resolver, rules));
            }
            return deps.build();
        }

        @Override
        public Object extractRuleKeyAppendablesFrom(final BuildTarget target, final CellPathResolver cellNames,
                final BuildRuleResolver resolver, FilterAndTargets inputs) throws MacroException {
            return getLinkerArgs(resolver, resolve(resolver, inputs.targets), inputs.filter);
        }

    }

    private static class CxxPlatformParseTimeDepsExpander extends StringExpander {

        private final FlavorDomain<CxxPlatform> cxxPlatforms;

        public CxxPlatformParseTimeDepsExpander(FlavorDomain<CxxPlatform> cxxPlatforms) {
            super("");
            this.cxxPlatforms = cxxPlatforms;
        }

        @Override
        public ImmutableList<BuildTarget> extractParseTimeDeps(BuildTarget target, CellPathResolver cellNames,
                ImmutableList<String> input) throws MacroException {
            Optional<CxxPlatform> platform = cxxPlatforms.getValue(target.getFlavors());
            return platform.isPresent() ? ImmutableList.copyOf(CxxPlatforms.getParseTimeDeps(platform.get()))
                    : ImmutableList.<BuildTarget>of();
        }
    }

    private static class FilterAndTargets {

        public final Optional<Pattern> filter;
        public final ImmutableList<BuildTarget> targets;

        public FilterAndTargets(Optional<Pattern> filter, ImmutableList<BuildTarget> targets) {
            this.filter = filter;
            this.targets = targets;
        }

    }

    private static class AsIsMacroReplacer implements MacroReplacer {

        private final String name;

        private AsIsMacroReplacer(String name) {
            this.name = name;
        }

        @Override
        public String replace(ImmutableList<String> args) throws MacroException {
            return String.format("$(%s %s)", name, Joiner.on(' ').join(args));
        }

    }

    private static class TargetTranslatorMacroReplacer implements MacroReplacer {

        private final AsIsMacroReplacer asIsMacroReplacer;
        private final Filter filter;
        private final BuildTargetPatternParser<?> buildTargetBuildTargetParser;
        private final CellPathResolver cellNames;
        private final TargetNodeTranslator translator;

        private TargetTranslatorMacroReplacer(AsIsMacroReplacer asIsMacroReplacer, Filter filter,
                BuildTargetPatternParser<?> buildTargetBuildTargetParser, CellPathResolver cellNames,
                TargetNodeTranslator translator) {
            this.asIsMacroReplacer = asIsMacroReplacer;
            this.filter = filter;
            this.buildTargetBuildTargetParser = buildTargetBuildTargetParser;
            this.cellNames = cellNames;
            this.translator = translator;
        }

        private BuildTarget parse(String input) throws MacroException {
            try {
                return BuildTargetParser.INSTANCE.parse(input, buildTargetBuildTargetParser, cellNames);
            } catch (BuildTargetParseException e) {
                throw new MacroException(e.getMessage(), e);
            }
        }

        @Override
        public String replace(ImmutableList<String> args) throws MacroException {
            ImmutableList.Builder<String> strings = ImmutableList.builder();

            if (filter == Filter.PARAM) {
                strings.add(args.get(0));
            }

            for (String arg : args.subList(filter == Filter.PARAM ? 1 : 0, args.size())) {
                BuildTarget target = parse(arg);
                strings.add(translator.translate(target).orElse(target).getFullyQualifiedName());
            }

            return asIsMacroReplacer.replace(strings.build());
        }

    }

    private enum Filter {
        NONE, PARAM,
    }

}