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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2015-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.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.ImmutableFlavor;
import com.facebook.buck.model.UnflavoredBuildTarget;
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.DependencyAggregation;
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.coercer.FrameworkPath;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.facebook.buck.util.immutables.BuckStyleTuple;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Suppliers;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
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.ImmutableSortedSet;
import com.google.common.collect.Lists;

import org.immutables.value.Value;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import javax.annotation.Nonnull;

@Value.Immutable
@BuckStyleImmutable
abstract class AbstractCxxSourceRuleFactory {

    private static final Logger LOG = Logger.get(AbstractCxxSourceRuleFactory.class);
    private static final String COMPILE_FLAVOR_PREFIX = "compile-";
    private static final Flavor AGGREGATED_PREPROCESS_DEPS_FLAVOR = ImmutableFlavor.of("preprocessor-deps");

    @Value.Parameter
    protected abstract BuildRuleParams getParams();

    @Value.Parameter
    protected abstract BuildRuleResolver getResolver();

    @Value.Parameter
    protected abstract SourcePathResolver getPathResolver();

    @Value.Parameter
    protected abstract SourcePathRuleFinder getRuleFinder();

    @Value.Parameter
    protected abstract CxxBuckConfig getCxxBuckConfig();

    @Value.Parameter
    protected abstract CxxPlatform getCxxPlatform();

    @Value.Parameter
    protected abstract ImmutableList<CxxPreprocessorInput> getCxxPreprocessorInput();

    @Value.Parameter
    protected abstract ImmutableMultimap<CxxSource.Type, String> getCompilerFlags();

    /** NOTE: {@code prefix_header} is incompatible with {@code precompiled_header}. */
    @Value.Parameter
    protected abstract Optional<SourcePath> getPrefixHeader();

    /** NOTE: {@code precompiled_header} is incompatible with {@code prefix_header}. */
    @Value.Parameter
    protected abstract Optional<SourcePath> getPrecompiledHeader();

    @Value.Parameter
    protected abstract PicType getPicType();

    @Value.Parameter
    protected abstract Optional<SymlinkTree> getSandboxTree();

    @Value.Check
    protected void checkPrefixAndPrecompiledHeaderArgs() {
        if (getPrefixHeader().isPresent() && getPrecompiledHeader().isPresent()) {
            throw new HumanReadableException(
                    "Cannot use `prefix_header` and `precompiled_header` in the same rule.");
        }
    }

    private ImmutableSortedSet<BuildRule> getPreprocessDeps() {
        ImmutableSortedSet.Builder<BuildRule> builder = ImmutableSortedSet.naturalOrder();
        for (CxxPreprocessorInput input : getCxxPreprocessorInput()) {
            builder.addAll(input.getDeps(getResolver(), getRuleFinder()));
        }
        if (getPrefixHeader().isPresent()) {
            builder.addAll(getRuleFinder().filterBuildRuleInputs(getPrefixHeader().get()));
        }
        if (getPrecompiledHeader().isPresent()) {
            builder.addAll(getRuleFinder().filterBuildRuleInputs(getPrecompiledHeader().get()));
        }
        if (getSandboxTree().isPresent()) {
            SymlinkTree tree = getSandboxTree().get();
            builder.add(tree);
            builder.addAll(getRuleFinder().filterBuildRuleInputs(tree.getLinks().values()));
        }
        return builder.build();
    }

    @Value.Lazy
    protected ImmutableSet<FrameworkPath> getFrameworks() {
        return getCxxPreprocessorInput().stream().flatMap(input -> input.getFrameworks().stream())
                .collect(MoreCollectors.toImmutableSet());
    }

    @Value.Lazy
    protected ImmutableList<CxxHeaders> getIncludes() {
        return getCxxPreprocessorInput().stream().flatMap(input -> input.getIncludes().stream())
                .collect(MoreCollectors.toImmutableList());
    }

    private final LoadingCache<CxxSource.Type, ImmutableList<String>> preprocessorFlags = CacheBuilder.newBuilder()
            .build(new CacheLoader<CxxSource.Type, ImmutableList<String>>() {
                @Override
                public ImmutableList<String> load(@Nonnull CxxSource.Type type) {
                    ImmutableList.Builder<String> builder = ImmutableList.builder();
                    for (CxxPreprocessorInput input : getCxxPreprocessorInput()) {
                        builder.addAll(input.getPreprocessorFlags().get(type));
                    }
                    return builder.build();
                }
            });

    private final LoadingCache<PreprocessorDelegateCacheKey, PreprocessorDelegateCacheValue> preprocessorDelegates = CacheBuilder
            .newBuilder().build(new PreprocessorDelegateCacheLoader());

    /**
     * Returns the no-op rule that aggregates the preprocessor dependencies.
     *
     * Individual compile rules can depend on it, instead of having to depend on every preprocessor
     * dep themselves. This turns O(n*m) dependencies into O(n+m) dependencies, where n is number of
     * files in a target, and m is the number of targets.
     */
    private BuildRule requireAggregatedPreprocessDepsRule() {
        BuildTarget target = createAggregatedPreprocessDepsBuildTarget();
        Optional<DependencyAggregation> existingRule = getResolver().getRuleOptionalWithType(target,
                DependencyAggregation.class);
        if (existingRule.isPresent()) {
            return existingRule.get();
        } else {
            BuildRuleParams params = getParams().copyWithChanges(target, Suppliers.ofInstance(getPreprocessDeps()),
                    Suppliers.ofInstance(ImmutableSortedSet.of()));
            DependencyAggregation rule = new DependencyAggregation(params, getPathResolver());
            getResolver().addToIndex(rule);
            return rule;
        }
    }

    @VisibleForTesting
    BuildTarget createAggregatedPreprocessDepsBuildTarget() {
        return BuildTarget.builder(getParams().getBuildTarget())
                .addFlavors(getCxxPlatform().getFlavor(), AGGREGATED_PREPROCESS_DEPS_FLAVOR).build();
    }

    private String getOutputName(String name) {
        List<String> parts = Lists.newArrayList();
        for (String part : Splitter.on(File.separator).omitEmptyStrings().split(name)) {
            // TODO(#7877540): Remove once we prevent disabling package boundary checks.
            parts.add(part.equals("..") ? "__PAR__" : part);
        }
        return Joiner.on(File.separator).join(parts);
    }

    /**
     * @return the object file name for the given source name.
     */
    private String getCompileOutputName(String name) {
        Linker ld = getCxxPlatform().getLd().resolve(getResolver());
        String outName = ld.hasFilePathSizeLimitations() ? "out" : getOutputName(name);
        return outName + "." + getCxxPlatform().getObjectFileExtension();
    }

    private String getCompileFlavorSuffix(String name) {
        return getOutputName(name) + "." + getCxxPlatform().getObjectFileExtension();
    }

    /**
     * @return the output path for an object file compiled from the source with the given name.
     */
    @VisibleForTesting
    Path getCompileOutputPath(BuildTarget target, String name) {
        return BuildTargets.getGenPath(getParams().getProjectFilesystem(), target, "%s")
                .resolve(getCompileOutputName(name));
    }

    /**
     * @return a build target for a {@link CxxPreprocessAndCompile} rule for the source with the
     * given name.
     */
    @VisibleForTesting
    public BuildTarget createCompileBuildTarget(String name) {
        String outputName = CxxFlavorSanitizer.sanitize(getCompileFlavorSuffix(name));
        return BuildTarget.builder(getParams().getBuildTarget()).addFlavors(getCxxPlatform().getFlavor())
                .addFlavors(ImmutableFlavor.of(String.format(COMPILE_FLAVOR_PREFIX + "%s%s",
                        getPicType() == PicType.PIC ? "pic-" : "", outputName)))
                .build();
    }

    public BuildTarget createInferCaptureBuildTarget(String name) {
        String outputName = CxxFlavorSanitizer.sanitize(getCompileFlavorSuffix(name));
        return BuildTarget.builder(getParams().getBuildTarget())
                .addAllFlavors(getParams().getBuildTarget().getFlavors()).addFlavors(getCxxPlatform().getFlavor())
                .addFlavors(ImmutableFlavor.of(String.format("%s-%s",
                        CxxInferEnhancer.InferFlavors.INFER_CAPTURE.get().toString(), outputName)))
                .build();
    }

    public static boolean isCompileFlavoredBuildTarget(BuildTarget target) {
        return target.getFlavors().stream().anyMatch(flavor -> flavor.getName().startsWith(COMPILE_FLAVOR_PREFIX));
    }

    private ImmutableList<String> getPlatformCompileFlags(CxxSource.Type type) {
        ImmutableList.Builder<String> args = ImmutableList.builder();

        // Add in the source-type specific platform compiler flags.
        args.addAll(CxxSourceTypes.getPlatformCompilerFlags(getCxxPlatform(), type));

        // These source types require assembling, so add in platform-specific assembler flags.
        //
        // TODO(andrewjcg): We shouldn't care about lower-level assembling.  If the user has assembler
        // flags in mind which they want to propagate to other languages, they should pass them in via
        // some other means (e.g. `.buckconfig`).
        if (type == CxxSource.Type.C_CPP_OUTPUT || type == CxxSource.Type.OBJC_CPP_OUTPUT
                || type == CxxSource.Type.CXX_CPP_OUTPUT || type == CxxSource.Type.OBJCXX_CPP_OUTPUT
                || type == CxxSource.Type.CUDA_CPP_OUTPUT) {
            args.addAll(getCxxPlatform().getAsflags());
        }

        return args.build();
    }

    private ImmutableList<String> getRuleCompileFlags(CxxSource.Type type) {
        return ImmutableList.copyOf(getCompilerFlags().get(type));
    }

    /**
     * @return a {@link CxxPreprocessAndCompile} rule that preprocesses, compiles, and assembles the
     * given {@link CxxSource}.
     */
    @VisibleForTesting
    public CxxPreprocessAndCompile createCompileBuildRule(String name, CxxSource source) {

        Preconditions.checkArgument(CxxSourceTypes.isCompilableType(source.getType()));

        BuildTarget target = createCompileBuildTarget(name);
        DepsBuilder depsBuilder = new DepsBuilder(getRuleFinder());

        Compiler compiler = CxxSourceTypes.getCompiler(getCxxPlatform(), source.getType()).resolve(getResolver());

        // Build up the list of compiler flags.
        CxxToolFlags flags = CxxToolFlags.explicitBuilder()
                // If we're using pic, add in the appropriate flag.
                .addAllPlatformFlags(getPicType().getFlags(compiler))
                // Add in the platform specific compiler flags.
                .addAllPlatformFlags(getPlatformCompileFlags(source.getType()))
                // Add custom compiler flags.
                .addAllRuleFlags(getRuleCompileFlags(source.getType()))
                // Add custom per-file flags.
                .addAllRuleFlags(source.getFlags()).build();

        CompilerDelegate compilerDelegate = new CompilerDelegate(getPathResolver(),
                getCxxPlatform().getCompilerDebugPathSanitizer(), compiler, flags);
        depsBuilder.add(compilerDelegate);

        depsBuilder.add(source);

        // Build the CxxCompile rule and add it to our sorted set of build rules.
        CxxPreprocessAndCompile result = CxxPreprocessAndCompile.compile(
                getParams().copyWithChanges(target, Suppliers.ofInstance(depsBuilder.build()),
                        Suppliers.ofInstance(ImmutableSortedSet.of())),
                getPathResolver(), compilerDelegate, getCompileOutputPath(target, name), source.getPath(),
                source.getType(), getCxxPlatform().getCompilerDebugPathSanitizer(),
                getCxxPlatform().getAssemblerDebugPathSanitizer(), getSandboxTree());
        getResolver().addToIndex(result);
        return result;
    }

    @VisibleForTesting
    CxxPreprocessAndCompile requireCompileBuildRule(String name, CxxSource source) {

        BuildTarget target = createCompileBuildTarget(name);
        Optional<CxxPreprocessAndCompile> existingRule = getResolver().getRuleOptionalWithType(target,
                CxxPreprocessAndCompile.class);
        if (existingRule.isPresent()) {
            if (!existingRule.get().getInput().equals(source.getPath())) {
                throw new RuntimeException(
                        String.format("Hash collision for %s; a build rule would have been ignored.", name));
            }
            return existingRule.get();
        }

        return createCompileBuildRule(name, source);

    }

    private CxxToolFlags computePreprocessorFlags(CxxSource.Type type, ImmutableList<String> sourceFlags) {
        Compiler compiler = CxxSourceTypes
                .getCompiler(getCxxPlatform(), CxxSourceTypes.getPreprocessorOutputType(type))
                .resolve(getResolver());
        return CxxToolFlags.explicitBuilder().addAllPlatformFlags(getPicType().getFlags(compiler))
                .addAllPlatformFlags(CxxSourceTypes.getPlatformPreprocessFlags(getCxxPlatform(), type))
                .addAllRuleFlags(preprocessorFlags.getUnchecked(type))
                // Add custom per-file flags.
                .addAllRuleFlags(sourceFlags).build();
    }

    private CxxToolFlags computeCompilerFlags(CxxSource.Type type, ImmutableList<String> sourceFlags) {
        AbstractCxxSource.Type outputType = CxxSourceTypes.getPreprocessorOutputType(type);
        return CxxToolFlags.explicitBuilder()
                // If we're using pic, add in the appropriate flag.
                .addAllPlatformFlags(getPicType()
                        .getFlags(CxxSourceTypes.getCompiler(getCxxPlatform(), outputType).resolve(getResolver())))
                // Add in the platform specific compiler flags.
                .addAllPlatformFlags(getPlatformCompileFlags(outputType))
                .addAllRuleFlags(getRuleCompileFlags(outputType)).addAllRuleFlags(sourceFlags).build();
    }

    private CxxInferCapture requireInferCaptureBuildRule(String name, CxxSource source,
            InferBuckConfig inferConfig) {
        BuildTarget target = createInferCaptureBuildTarget(name);

        Optional<CxxInferCapture> existingRule = getResolver().getRuleOptionalWithType(target,
                CxxInferCapture.class);
        if (existingRule.isPresent()) {
            return existingRule.get();
        }

        return createInferCaptureBuildRule(target, name, source, inferConfig);
    }

    private CxxInferCapture createInferCaptureBuildRule(BuildTarget target, String name, CxxSource source,
            InferBuckConfig inferConfig) {
        Preconditions.checkArgument(CxxSourceTypes.isPreprocessableType(source.getType()));

        LOG.verbose("Creating preprocessed InferCapture build rule %s for %s", target, source);

        DepsBuilder depsBuilder = new DepsBuilder(getRuleFinder());
        depsBuilder.add(requireAggregatedPreprocessDepsRule());

        PreprocessorDelegateCacheValue preprocessorDelegateValue = preprocessorDelegates
                .getUnchecked(PreprocessorDelegateCacheKey.of(source.getType(), source.getFlags()));
        depsBuilder.add(preprocessorDelegateValue.getPreprocessorDelegate());

        CxxToolFlags ppFlags = CxxToolFlags.copyOf(
                CxxSourceTypes.getPlatformPreprocessFlags(getCxxPlatform(), source.getType()),
                preprocessorFlags.getUnchecked(source.getType()));

        CxxToolFlags cFlags = computeCompilerFlags(source.getType(), source.getFlags());

        depsBuilder.add(source);

        CxxInferCapture result = new CxxInferCapture(
                getParams().copyWithChanges(target, Suppliers.ofInstance(depsBuilder.build()),
                        Suppliers.ofInstance(ImmutableSortedSet.of())),
                getPathResolver(), ppFlags, cFlags, source.getPath(), source.getType(),
                getCompileOutputPath(target, name), preprocessorDelegateValue.getPreprocessorDelegate(),
                inferConfig, getCxxPlatform().getCompilerDebugPathSanitizer());
        getResolver().addToIndex(result);
        return result;
    }

    /**
     * @return a {@link CxxPreprocessAndCompile} rule that preprocesses, compiles, and assembles the
     * given {@link CxxSource}.
     */
    @VisibleForTesting
    public CxxPreprocessAndCompile createPreprocessAndCompileBuildRule(String name, CxxSource source) {

        BuildTarget target = createCompileBuildTarget(name);
        LOG.verbose("Creating preprocess and compile %s for %s", target, source);
        Preconditions.checkArgument(CxxSourceTypes.isPreprocessableType(source.getType()));

        DepsBuilder depsBuilder = new DepsBuilder(getRuleFinder());
        depsBuilder.add(requireAggregatedPreprocessDepsRule());

        CompilerDelegate compilerDelegate = new CompilerDelegate(getPathResolver(),
                getCxxPlatform().getCompilerDebugPathSanitizer(),
                CxxSourceTypes
                        .getCompiler(getCxxPlatform(), CxxSourceTypes.getPreprocessorOutputType(source.getType()))
                        .resolve(getResolver()),
                computeCompilerFlags(source.getType(), source.getFlags()));
        depsBuilder.add(compilerDelegate);

        PreprocessorDelegateCacheValue preprocessorDelegateValue = preprocessorDelegates
                .getUnchecked(PreprocessorDelegateCacheKey.of(source.getType(), source.getFlags()));
        PreprocessorDelegate preprocessorDelegate = preprocessorDelegateValue.getPreprocessorDelegate();
        depsBuilder.add(preprocessorDelegate);

        depsBuilder.add(source);

        Preprocessor preprocessor = preprocessorDelegate.getPreprocessor();

        if (getPrecompiledHeader().isPresent() && !canUsePrecompiledHeaders(getCxxBuckConfig(), preprocessor)) {
            throw new HumanReadableException(
                    "Precompiled header was requested for this rule, but PCH's are not possible under "
                            + "the current environment (preprocessor/compiler, and/or 'cxx.pch_enabled' option).");
        }

        Optional<PrecompiledHeaderReference> precompiledHeaderReference = Optional.empty();
        if (canUsePrecompiledHeaders(getCxxBuckConfig(), preprocessor)
                && (getPrefixHeader().isPresent() || getPrecompiledHeader().isPresent())) {
            CxxPrecompiledHeader precompiledHeader = requirePrecompiledHeaderBuildRule(preprocessorDelegateValue,
                    source);
            depsBuilder.add(precompiledHeader);
            precompiledHeaderReference = Optional.of(PrecompiledHeaderReference.of(precompiledHeader));
            if (getPrecompiledHeader().isPresent()) {
                // For a precompiled header (and not a prefix header), we may need extra include paths.
                // The PCH build might have involved some deps that this rule does not have, so we
                // would need to pull in its include paths to ensure any includes that happen during this
                // build play out the same way as they did for the PCH.
                try {
                    preprocessorDelegate = preprocessorDelegate
                            .withLeadingIncludePaths(precompiledHeader.getCxxIncludePaths());
                } catch (PreprocessorDelegate.ConflictingHeadersException e) {
                    throw e.getHumanReadableExceptionForBuildTarget(getParams().getBuildTarget());
                }
            }
        }

        // Build the CxxCompile rule and add it to our sorted set of build rules.
        CxxPreprocessAndCompile result = CxxPreprocessAndCompile.preprocessAndCompile(
                getParams().copyWithChanges(target, Suppliers.ofInstance(depsBuilder.build()),
                        Suppliers.ofInstance(ImmutableSortedSet.of())),
                getPathResolver(), preprocessorDelegate, compilerDelegate, getCompileOutputPath(target, name),
                source.getPath(), source.getType(), precompiledHeaderReference,
                getCxxPlatform().getCompilerDebugPathSanitizer(), getCxxPlatform().getAssemblerDebugPathSanitizer(),
                getSandboxTree());
        getResolver().addToIndex(result);
        return result;
    }

    @VisibleForTesting
    CxxPreprocessAndCompile requirePreprocessAndCompileBuildRule(String name, CxxSource source) {

        BuildTarget target = createCompileBuildTarget(name);
        Optional<CxxPreprocessAndCompile> existingRule = getResolver().getRuleOptionalWithType(target,
                CxxPreprocessAndCompile.class);
        if (existingRule.isPresent()) {
            if (!existingRule.get().getInput().equals(source.getPath())) {
                throw new RuntimeException(
                        String.format("Hash collision for %s; a build rule would have been ignored.", name));
            }
            return existingRule.get();
        }

        return createPreprocessAndCompileBuildRule(name, source);
    }

    /**
     * Look up or build a precompiled header build rule which this build rule is requesting.
     *
     * The PCH is requested either via a {@code prefix_header='<em>pathToHeaderFileOrTarget</em>'},
     * transparently converting the prefix header to a precompiled header, or a precompiled header
     * requested with {@code precompiled_header='<em>//:ruleToPCHTemplate</em>'}.
     *
     * @param preprocessorDelegateCacheValue
     * @param source One of the sources belonging to the rule for which we are building this PCH.
     *        Needed in order to obtain the language type and compiler flags for PCH itself.
     *        Used in the {@link CxxPreprocessAndCompile} rule which builds that source.
     * @see #buildPrecompiledHeaderFromPrefixHeader(
     *          PreprocessorDelegateCacheValue, CxxSource, DepsBuilder, SourcePath)
     * @see #buildPrecompiledHeaderFromTemplateRule(
     *          PreprocessorDelegateCacheValue, CxxSource, DepsBuilder, SourcePath)
     */
    private CxxPrecompiledHeader requirePrecompiledHeaderBuildRule(
            PreprocessorDelegateCacheValue preprocessorDelegateCacheValue, CxxSource source) {

        Preconditions.checkState(getPrefixHeader().isPresent() ^ getPrecompiledHeader().isPresent());

        // Compilers only accept precompiled headers generated with the same flags and language options.
        // As such, each prefix header may generate multiple pch files, and need unique build targets
        // to be differentiated in the build graph.

        DepsBuilder depsBuilder = new DepsBuilder(getRuleFinder());
        depsBuilder.add(preprocessorDelegateCacheValue.getPreprocessorDelegate());
        depsBuilder.add(requireAggregatedPreprocessDepsRule());

        return getPrefixHeader().isPresent()
                ? buildPrecompiledHeaderFromPrefixHeader(preprocessorDelegateCacheValue, source, depsBuilder,
                        getPrefixHeader().get())
                : buildPrecompiledHeaderFromTemplateRule(preprocessorDelegateCacheValue, source, depsBuilder,
                        getPrecompiledHeader().get());
    }

    private CxxPrecompiledHeader buildPrecompiledHeaderFromPrefixHeader(
            PreprocessorDelegateCacheValue preprocessorDelegateCacheValue, CxxSource source,
            DepsBuilder depsBuilder, SourcePath headerPath) {

        CxxSource.Type sourceType = source.getType();
        CxxToolFlags compilerFlags = computeCompilerFlags(sourceType, source.getFlags());

        // Language needs to be part of the key, PCHs built under a different language are incompatible.
        // (Replace `c++` with `cxx`; avoid default scrubbing which would make it the cryptic `c__`.)
        final String langCode = sourceType.getLanguage().replaceAll("c\\+\\+", "cxx");

        final String pchBaseID = "pch-" + langCode + "-"
                + preprocessorDelegateCacheValue.getBaseHash(compilerFlags);
        final String pchFullID = pchBaseID + "-" + preprocessorDelegateCacheValue.getFullHash(compilerFlags);

        return buildPrecompiledHeader(preprocessorDelegateCacheValue.getPreprocessorDelegate(), sourceType,
                compilerFlags, headerPath, depsBuilder, getParams().getBuildTarget().getUnflavoredBuildTarget(),
                ImmutableSortedSet.of(getCxxPlatform().getFlavor(),
                        ImmutableFlavor.of(Flavor.replaceInvalidCharacters(pchFullID))));
    }

    /**
     * Build a PCH rule, given a {@code cxx_precompiled_header} rule.
     *
     * <p>
     * We'll "instantiate" this PCH from this template, using the parameters (src, dependencies)
     * from the template itself, plus the build flags that are used in the current build rule
     * (so that this instantiated version uses compatible build flags and thus the PCH is guaranteed
     * usable with this rule).
     * </p>
     */
    private CxxPrecompiledHeader buildPrecompiledHeaderFromTemplateRule(
            PreprocessorDelegateCacheValue preprocessorDelegateCacheValue, CxxSource source,
            DepsBuilder depsBuilder, SourcePath headerTargetPath) {

        PreprocessorDelegate preprocessorDelegate = preprocessorDelegateCacheValue.getPreprocessorDelegate();

        CxxToolFlags compilerFlags = CxxToolFlags.concat(
                preprocessorDelegate.getNonIncludePathFlags(/* no pch */ Optional.empty()),
                computeCompilerFlags(source.getType(), source.getFlags()));

        BuildTarget pchTemplateTarget = ((BuildTargetSourcePath) headerTargetPath).getTarget();
        Optional<CxxPrecompiledHeaderTemplate> pchTemplateRuleOpt = getResolver()
                .getRuleOptionalWithType(pchTemplateTarget, CxxPrecompiledHeaderTemplate.class);
        Preconditions.checkState(pchTemplateRuleOpt.isPresent());
        CxxPrecompiledHeaderTemplate pchTemplate = pchTemplateRuleOpt.get();

        SourcePath headerPath = pchTemplate.sourcePath;

        // Language needs to be part of the key, PCHs built under a different language are incompatible.
        // (Replace `c++` with `cxx`; avoid default scrubbing which would make it the cryptic `c__`.)
        final String langCode = source.getType().getLanguage().replaceAll("c\\+\\+", "cxx");
        final String pchBaseID = "pch-" + langCode + "-"
                + preprocessorDelegateCacheValue.getBaseHash(compilerFlags);

        for (BuildRule rule : pchTemplate.getDeps()) {
            depsBuilder.add(rule);
        }

        return buildPrecompiledHeader(preprocessorDelegateCacheValue.getPreprocessorDelegate(), source.getType(),
                compilerFlags, headerPath, depsBuilder, pchTemplateTarget.getUnflavoredBuildTarget(),
                ImmutableSortedSet.of(getCxxPlatform().getFlavor(),
                        ImmutableFlavor.of(Flavor.replaceInvalidCharacters(pchBaseID))));
    }

    /**
     * Look up or build a precompiled header build rule which this build rule is requesting.
     *
     * <p>
     * This method will first try to determine whether a matching PCH was already created;
     * if so, it will be reused.  This is done by searching the cache in the {@link BuildRuleResolver}
     * owned by this class.  If this ends up building a new instance of {@link CxxPrecompiledHeader},
     * it will be added to the resolver cache.
     * </p>
     */
    private CxxPrecompiledHeader buildPrecompiledHeader(PreprocessorDelegate preprocessorDelegate,
            CxxSource.Type sourceType, CxxToolFlags compilerFlags, SourcePath headerPath, DepsBuilder depsBuilder,
            UnflavoredBuildTarget templateTarget, ImmutableSortedSet<Flavor> flavors) {

        BuildTarget target = BuildTarget.builder(templateTarget).addAllFlavors(flavors).build();

        Optional<CxxPrecompiledHeader> existingRule = getResolver().getRuleOptionalWithType(target,
                CxxPrecompiledHeader.class);
        if (existingRule.isPresent()) {
            return existingRule.get();
        }

        // Give the PCH a filename that looks like a header file with .gch appended to it, GCC-style.
        // GCC accepts an "-include" flag with the .h file as its arg, and auto-appends ".gch" to
        // automagically use the precompiled header in place of the original header.  Of course in
        // our case we'll only have the ".gch" file, which is alright; the ".h" isn't truly needed.
        Path output = BuildTargets.getGenPath(getParams().getProjectFilesystem(), target, "%s.h.gch");

        CompilerDelegate compilerDelegate = new CompilerDelegate(getPathResolver(),
                getCxxPlatform().getCompilerDebugPathSanitizer(),
                CxxSourceTypes.getCompiler(getCxxPlatform(), CxxSourceTypes.getPreprocessorOutputType(sourceType))
                        .resolve(getResolver()),
                compilerFlags);
        depsBuilder.add(compilerDelegate);

        depsBuilder.add(headerPath);

        BuildRuleParams params = getParams().copyWithChanges(target, Suppliers.ofInstance(depsBuilder.build()),
                Suppliers.ofInstance(ImmutableSortedSet.of()));

        CxxPrecompiledHeader rule = new CxxPrecompiledHeader(params, getPathResolver(), output,
                preprocessorDelegate, compilerDelegate, compilerFlags, headerPath, sourceType,
                getCxxPlatform().getCompilerDebugPathSanitizer(),
                getCxxPlatform().getAssemblerDebugPathSanitizer());

        getResolver().addToIndex(rule);

        return rule;
    }

    public ImmutableSet<CxxInferCapture> requireInferCaptureBuildRules(ImmutableMap<String, CxxSource> sources,
            InferBuckConfig inferConfig, CxxInferSourceFilter sourceFilter) {

        ImmutableSet.Builder<CxxInferCapture> objects = ImmutableSet.builder();

        for (Map.Entry<String, CxxSource> entry : sources.entrySet()) {
            String name = entry.getKey();
            CxxSource source = entry.getValue();

            Preconditions.checkState(CxxSourceTypes.isPreprocessableType(source.getType()),
                    "Only preprocessable source types are currently supported");

            if (sourceFilter.isBlacklisted(source)) {
                continue;
            }

            CxxInferCapture rule = requireInferCaptureBuildRule(name, source, inferConfig);
            objects.add(rule);
        }

        return objects.build();
    }

    @VisibleForTesting
    ImmutableMap<CxxPreprocessAndCompile, SourcePath> requirePreprocessAndCompileRules(
            ImmutableMap<String, CxxSource> sources) {

        return sources.entrySet().stream().map(entry -> {
            String name = entry.getKey();
            CxxSource source = entry.getValue();

            Preconditions.checkState(CxxSourceTypes.isPreprocessableType(source.getType())
                    || CxxSourceTypes.isCompilableType(source.getType()));

            source = getSandboxedCxxSource(source);

            // If it's a preprocessable source, use a combine preprocess-and-compile build rule.
            // Otherwise, use a regular compile rule.
            if (CxxSourceTypes.isPreprocessableType(source.getType())) {
                return requirePreprocessAndCompileBuildRule(name, source);
            } else {
                return requireCompileBuildRule(name, source);
            }
        }).collect(MoreCollectors.toImmutableMap(Function.identity(),
                input -> new BuildTargetSourcePath(input.getBuildTarget())));
    }

    private CxxSource getSandboxedCxxSource(CxxSource source) {
        if (getSandboxTree().isPresent()) {
            SymlinkTree sandboxTree = getSandboxTree().get();
            Path sourcePath = Paths
                    .get(getPathResolver().getSourcePathName(getParams().getBuildTarget(), source.getPath()));
            Path sandboxPath = BuildTargets.getGenPath(getParams().getProjectFilesystem(),
                    sandboxTree.getBuildTarget(), "%s");
            BuildTargetSourcePath path = new BuildTargetSourcePath(sandboxTree.getBuildTarget(),
                    sandboxPath.resolve(sourcePath));
            source = CxxSource.copyOf(source).withPath(path);
        }
        return source;
    }

    /**
     * Can PCH headers be used with the current configuration and type of compiler?
     */
    private boolean canUsePrecompiledHeaders(CxxBuckConfig cxxBuckConfig, Preprocessor preprocessor) {
        return cxxBuckConfig.isPCHEnabled() && preprocessor.supportsPrecompiledHeaders();
    }

    public static ImmutableMap<CxxPreprocessAndCompile, SourcePath> requirePreprocessAndCompileRules(
            BuildRuleParams params, BuildRuleResolver resolver, SourcePathResolver pathResolver,
            SourcePathRuleFinder ruleFinder, CxxBuckConfig cxxBuckConfig, CxxPlatform cxxPlatform,
            ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput,
            ImmutableMultimap<CxxSource.Type, String> compilerFlags, Optional<SourcePath> prefixHeader,
            Optional<SourcePath> precompiledHeader, ImmutableMap<String, CxxSource> sources, PicType pic,
            Optional<SymlinkTree> sandboxTree) {
        CxxSourceRuleFactory factory = CxxSourceRuleFactory.of(params, resolver, pathResolver, ruleFinder,
                cxxBuckConfig, cxxPlatform, cxxPreprocessorInput, compilerFlags, prefixHeader, precompiledHeader,
                pic, sandboxTree);
        return factory.requirePreprocessAndCompileRules(sources);
    }

    public enum PicType {

        // Generate position-independent code (e.g. for use in shared libraries).
        PIC {
            @Override
            public ImmutableList<String> getFlags(Compiler compiler) {
                return compiler.getPicFlags();
            }
        },

        // Generate position-dependent code.
        PDC {
            @Override
            public ImmutableList<String> getFlags(Compiler compiler) {
                return compiler.getPdcFlags();
            }
        };

        abstract ImmutableList<String> getFlags(Compiler compiler);
    }

    @Value.Immutable
    @BuckStyleTuple
    interface AbstractPreprocessorDelegateCacheKey {
        CxxSource.Type getSourceType();

        ImmutableList<String> getSourceFlags();
    }

    @VisibleForTesting
    public ImmutableList<String> getFlagsForSource(CxxSource source, boolean allowIncludePathFlags) {
        PreprocessorDelegateCacheValue preprocessorDelegateValue = preprocessorDelegates
                .getUnchecked(PreprocessorDelegateCacheKey.of(source.getType(), source.getFlags()));
        CxxToolFlags flags = computeCompilerFlags(source.getType(), source.getFlags());
        PreprocessorDelegateCacheValue.HashStrings hashStrings = preprocessorDelegateValue.get(flags);
        return allowIncludePathFlags ? hashStrings.fullFlags : hashStrings.baseFlags;
    }

    static class PreprocessorDelegateCacheValue {
        private final PreprocessorDelegate preprocessorDelegate;
        private final LoadingCache<CxxToolFlags, HashStrings> commandHashCache;

        class HashStrings {
            /** List of build flags (as strings), except for those related to header search paths. */
            public final ImmutableList<String> baseFlags;
            /** Complete list of all build flags (as strings), including header search paths. */
            public final ImmutableList<String> fullFlags;
            public final String baseHash;
            public final String fullHash;

            public HashStrings(CxxToolFlags compilerFlags) {
                ImmutableList.Builder<String> builder = ImmutableList.<String>builder();

                // Add the build command itself first
                builder.addAll(preprocessorDelegate.getCommandPrefix());
                // Then preprocessor + compiler args, not including include path args like -I, -isystem, ...
                builder.addAll(preprocessorDelegate.getNonIncludePathFlags(Optional.empty()).getAllFlags());
                builder.addAll(compilerFlags.getAllFlags());
                // Output what we have so far, to this list, then hash it.
                this.baseFlags = builder.build();
                this.baseHash = preprocessorDelegate.hashCommand(this.baseFlags).substring(0, 10);

                // Continue building.  Using the same builder; add header search paths, to the above flags.
                builder.addAll(preprocessorDelegate.getIncludePathFlags().getAllFlags());
                // Output this super-set of flags to this list, then hash it.
                this.fullFlags = builder.build();
                this.fullHash = preprocessorDelegate.hashCommand(this.fullFlags).substring(0, 10);
            }
        }

        PreprocessorDelegateCacheValue(PreprocessorDelegate preprocessorDelegate) {
            this.preprocessorDelegate = preprocessorDelegate;
            this.commandHashCache = CacheBuilder.newBuilder().build(new CacheLoader<CxxToolFlags, HashStrings>() {
                @Override
                public HashStrings load(CxxToolFlags key) {
                    // Note: this hash call is mainly for the benefit of precompiled headers, to produce
                    // the PCH's hash of build flags.  (Since there's no PCH yet, the PCH argument is
                    // passed as empty here.)
                    return new HashStrings(key);
                }
            });
        }

        PreprocessorDelegate getPreprocessorDelegate() {
            return preprocessorDelegate;
        }

        @VisibleForTesting
        public HashStrings get(CxxToolFlags flags) {
            return this.commandHashCache.getUnchecked(flags);
        }

        String getBaseHash(CxxToolFlags flags) {
            return get(flags).baseHash;
        }

        String getFullHash(CxxToolFlags flags) {
            return get(flags).fullHash;
        }
    }

    private class PreprocessorDelegateCacheLoader
            extends CacheLoader<PreprocessorDelegateCacheKey, PreprocessorDelegateCacheValue> {

        @Override
        public PreprocessorDelegateCacheValue load(@Nonnull PreprocessorDelegateCacheKey key) {
            Preprocessor preprocessor = CxxSourceTypes.getPreprocessor(getCxxPlatform(), key.getSourceType())
                    .resolve(getResolver());
            try {
                PreprocessorDelegate delegate = new PreprocessorDelegate(getPathResolver(),
                        getCxxPlatform().getCompilerDebugPathSanitizer(), getCxxPlatform().getHeaderVerification(),
                        getParams().getProjectFilesystem().getRootPath(), preprocessor,
                        PreprocessorFlags.of(getPrefixHeader(),
                                computePreprocessorFlags(key.getSourceType(), key.getSourceFlags()), getIncludes(),
                                getFrameworks()),
                        CxxDescriptionEnhancer.frameworkPathToSearchPath(getCxxPlatform(), getPathResolver()),
                        getSandboxTree(), /* leadingIncludePaths */ Optional.empty());
                return new PreprocessorDelegateCacheValue(delegate);
            } catch (PreprocessorDelegate.ConflictingHeadersException e) {
                throw e.getHumanReadableExceptionForBuildTarget(getParams().getBuildTarget());
            }
        }
    }

}