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

Java tutorial

Introduction

Here is the source code for com.facebook.buck.cxx.CxxSourceRuleFactory.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.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRules;
import com.facebook.buck.rules.BuildTargetSourcePath;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.coercer.FrameworkPath;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
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.FluentIterable;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import org.immutables.value.Value;

import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;

public class CxxSourceRuleFactory {

    private static final Logger LOG = Logger.get(CxxSourceRuleFactory.class);
    private static final String COMPILE_FLAVOR_PREFIX = "compile-";
    private static final String PREPROCESS_FLAVOR_PREFIX = "preprocess-";

    private final BuildRuleParams params;
    private final BuildRuleResolver resolver;
    private final SourcePathResolver pathResolver;
    private final CxxPlatform cxxPlatform;
    private final ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput;
    private final ImmutableList<String> compilerFlags;
    private final Optional<SourcePath> prefixHeader;

    private final Supplier<ImmutableList<BuildRule>> preprocessDeps = Suppliers
            .memoize(new Supplier<ImmutableList<BuildRule>>() {
                @Override
                public ImmutableList<BuildRule> get() {
                    ImmutableList.Builder<BuildRule> builder = ImmutableList.builder();

                    for (CxxPreprocessorInput input : cxxPreprocessorInput) {

                        // Depend on the rules that generate the sources and headers we're compiling.
                        builder.addAll(pathResolver.filterBuildRuleInputs(ImmutableList.<SourcePath>builder()
                                .addAll(input.getIncludes().getNameToPathMap().values()).build()));

                        // Also add in extra deps from the preprocessor input, such as the symlink tree
                        // rules.
                        builder.addAll(
                                BuildRules.toBuildRulesFor(params.getBuildTarget(), resolver, input.getRules()));
                    }

                    return builder.build();
                }
            });

    private final Supplier<ImmutableSet<Path>> includeRoots = Suppliers.memoize(new Supplier<ImmutableSet<Path>>() {
        @Override
        public ImmutableSet<Path> get() {
            return FluentIterable.from(cxxPreprocessorInput)
                    .transformAndConcat(CxxPreprocessorInput.GET_INCLUDE_ROOTS).toSet();
        }
    });

    private final Supplier<ImmutableSet<Path>> systemIncludeRoots = Suppliers
            .memoize(new Supplier<ImmutableSet<Path>>() {
                @Override
                public ImmutableSet<Path> get() {
                    return FluentIterable.from(cxxPreprocessorInput)
                            .transformAndConcat(CxxPreprocessorInput.GET_SYSTEM_INCLUDE_ROOTS).toSet();
                }
            });

    private final Supplier<ImmutableSet<Path>> headerMaps = Suppliers.memoize(new Supplier<ImmutableSet<Path>>() {
        @Override
        public ImmutableSet<Path> get() {
            return FluentIterable.from(cxxPreprocessorInput)
                    .transformAndConcat(CxxPreprocessorInput.GET_HEADER_MAPS).toSet();
        }
    });

    private final Supplier<ImmutableSet<FrameworkPath>> frameworks = Suppliers
            .memoize(new Supplier<ImmutableSet<FrameworkPath>>() {
                @Override
                public ImmutableSet<FrameworkPath> get() {
                    return FluentIterable.from(cxxPreprocessorInput)
                            .transformAndConcat(CxxPreprocessorInput.GET_FRAMEWORKS).toSet();
                }
            });

    private final Supplier<ImmutableList<CxxHeaders>> includes = Suppliers
            .memoize(new Supplier<ImmutableList<CxxHeaders>>() {
                @Override
                public ImmutableList<CxxHeaders> get() {
                    return FluentIterable.from(cxxPreprocessorInput).transform(CxxPreprocessorInput.GET_INCLUDES)
                            .toList();
                }
            });

    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 : cxxPreprocessorInput) {
                        builder.addAll(input.getPreprocessorFlags().get(type));
                    }
                    return builder.build();
                }
            });

    private final LoadingCache<CxxSource.Type, PreprocessorDelegate> preprocessAndCompilePreprocessorDelegate;
    private final LoadingCache<PreprocessAndCompilePreprocessorDelegateKey, PreprocessorDelegate> preprocessPreprocessorDelegate;

    @VisibleForTesting
    public CxxSourceRuleFactory(BuildRuleParams params, BuildRuleResolver resolver, SourcePathResolver pathResolver,
            CxxPlatform cxxPlatform, ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput,
            ImmutableList<String> compilerFlags, Optional<SourcePath> prefixHeader) {
        this.params = params;
        this.resolver = resolver;
        this.pathResolver = pathResolver;
        this.cxxPlatform = cxxPlatform;
        this.cxxPreprocessorInput = cxxPreprocessorInput;
        this.compilerFlags = compilerFlags;
        this.prefixHeader = prefixHeader;
        this.preprocessAndCompilePreprocessorDelegate = CacheBuilder.newBuilder()
                .build(new PreprocessAndCompileCacheLoader());
        this.preprocessPreprocessorDelegate = CacheBuilder.newBuilder()
                .build(new PreprocessPreprocessorCacheLoader());
    }

    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 preprocessed file name for the given source name.
     */
    private String getPreprocessOutputName(CxxSource.Type type, String name) {
        CxxSource.Type outputType = CxxSourceTypes.getPreprocessorOutputType(type);
        return getOutputName(name) + "." + Iterables.get(outputType.getExtensions(), 0);
    }

    /**
     * @return a {@link BuildTarget} used for the rule that preprocesses the source by the given
     *     name and type.
     */
    @VisibleForTesting
    public BuildTarget createPreprocessBuildTarget(String name, CxxSource.Type type, PicType pic) {
        String outputName = Flavor.replaceInvalidCharacters(getPreprocessOutputName(type, name));
        return BuildTarget.builder(params.getBuildTarget()).addFlavors(cxxPlatform.getFlavor())
                .addFlavors(ImmutableFlavor.of(String.format(PREPROCESS_FLAVOR_PREFIX + "%s%s",
                        pic == PicType.PIC ? "pic-" : "", outputName)))
                .build();
    }

    public static boolean isPreprocessFlavoredBuildTarget(BuildTarget target) {
        Set<Flavor> flavors = target.getFlavors();
        for (Flavor flavor : flavors) {
            if (flavor.getName().startsWith(PREPROCESS_FLAVOR_PREFIX)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return the output path for an object file compiled from the source with the given name.
     */
    @VisibleForTesting
    Path getPreprocessOutputPath(BuildTarget target, CxxSource.Type type, String name) {
        return BuildTargets.getGenPath(target, "%s").resolve(getPreprocessOutputName(type, name));
    }

    @VisibleForTesting
    public CxxPreprocessAndCompile createPreprocessBuildRule(BuildRuleResolver resolver, String name,
            CxxSource source, PicType pic) {

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

        BuildTarget target = createPreprocessBuildTarget(name, source.getType(), pic);
        Preprocessor tool = CxxSourceTypes.getPreprocessor(cxxPlatform, source.getType());

        // Build up the list of dependencies for this rule.
        ImmutableSortedSet<BuildRule> dependencies = computeSourcePreprocessorAndToolDeps(Optional.of((Tool) tool),
                source);

        // Build the CxxCompile rule and add it to our sorted set of build rules.
        CxxPreprocessAndCompile result = CxxPreprocessAndCompile.preprocess(params.copyWithChanges(target,
                Suppliers.ofInstance(dependencies), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())),
                pathResolver,
                preprocessPreprocessorDelegate.getUnchecked(
                        PreprocessAndCompilePreprocessorDelegateKey.of(source.getType(), pic, source.getFlags())),
                getPreprocessOutputPath(target, source.getType(), name), source.getPath(), source.getType(),
                cxxPlatform.getDebugPathSanitizer());
        resolver.addToIndex(result);
        return result;
    }

    @VisibleForTesting
    CxxPreprocessAndCompile requirePreprocessBuildRule(BuildRuleResolver resolver, String name, CxxSource source,
            PicType pic) {

        BuildTarget target = createPreprocessBuildTarget(name, source.getType(), pic);
        Optional<CxxPreprocessAndCompile> existingRule = resolver.getRuleOptionalWithType(target,
                CxxPreprocessAndCompile.class);
        if (existingRule.isPresent()) {
            return existingRule.get();
        }

        return createPreprocessBuildRule(resolver, name, source, pic);
    }

    /**
     * @return the object file name for the given source name.
     */
    private String getCompileOutputName(String name) {
        return getOutputName(name) + ".o";
    }

    /**
     * @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(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, PicType pic) {
        String outputName = Flavor.replaceInvalidCharacters(getCompileOutputName(name));
        return BuildTarget.builder(params.getBuildTarget()).addFlavors(cxxPlatform.getFlavor())
                .addFlavors(ImmutableFlavor.of(String.format(COMPILE_FLAVOR_PREFIX + "%s%s",
                        pic == PicType.PIC ? "pic-" : "", outputName)))
                .build();
    }

    public BuildTarget createInferCaptureBuildTarget(String name) {
        String outputName = Flavor.replaceInvalidCharacters(getCompileOutputName(name));
        return BuildTarget.builder(params.getBuildTarget()).addAllFlavors(params.getBuildTarget().getFlavors())
                .addFlavors(cxxPlatform.getFlavor())
                .addFlavors(ImmutableFlavor.of(String.format("infer-capture-%s", outputName))).build();
    }

    public static boolean isCompileFlavoredBuildTarget(BuildTarget target) {
        Set<Flavor> flavors = target.getFlavors();
        for (Flavor flavor : flavors) {
            if (flavor.getName().startsWith(COMPILE_FLAVOR_PREFIX)) {
                return true;
            }
        }
        return false;
    }

    // Pick the compiler to use.  Basically, if we're dealing with C++ sources, use the C++
    // compiler, and the C compiler for everything.
    private Compiler getCompiler(CxxSource.Type type) {
        return CxxSourceTypes.needsCxxCompiler(type) ? cxxPlatform.getCxx() : cxxPlatform.getCc();
    }

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

        // If we're dealing with a C source that can be compiled, add the platform C compiler flags.
        if (type == CxxSource.Type.C_CPP_OUTPUT || type == CxxSource.Type.OBJC_CPP_OUTPUT) {
            args.addAll(cxxPlatform.getCflags());
        }

        // If we're dealing with a C++ source that can be compiled, add the platform C++ compiler
        // flags.
        if (type == CxxSource.Type.CXX_CPP_OUTPUT || type == CxxSource.Type.OBJCXX_CPP_OUTPUT) {
            args.addAll(cxxPlatform.getCxxflags());
        }

        // All source types require assembling, so add in platform-specific assembler flags.
        args.addAll(cxxPlatform.getAsflags());

        return args.build();
    }

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

        // Add in explicit additional compiler flags, if we're compiling.
        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) {
            args.addAll(compilerFlags);
        }

        return args.build();
    }

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

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

        BuildTarget target = createCompileBuildTarget(name, pic);
        Compiler compiler = getCompiler(source.getType());

        ImmutableSortedSet<BuildRule> dependencies = ImmutableSortedSet.<BuildRule>naturalOrder()
                // Add dependencies on any build rules used to create the compiler.
                .addAll(compiler.getDeps(pathResolver))
                // If a build rule generates our input source, add that as a dependency.
                .addAll(pathResolver.filterBuildRuleInputs(source.getPath())).build();

        // Build up the list of compiler flags.
        ImmutableList<String> platformFlags = ImmutableList.<String>builder()
                // If we're using pic, add in the appropriate flag.
                .addAll(pic.getFlags())
                // Add in the platform specific compiler flags.
                .addAll(getPlatformCompileFlags(source.getType())).build();

        ImmutableList<String> ruleFlags = ImmutableList.<String>builder()
                // Add custom compiler flags.
                .addAll(getRuleCompileFlags(source.getType()))
                // Add custom per-file flags.
                .addAll(source.getFlags()).build();

        // Build the CxxCompile rule and add it to our sorted set of build rules.
        CxxPreprocessAndCompile result = CxxPreprocessAndCompile.compile(
                params.copyWithChanges(target, Suppliers.ofInstance(dependencies),
                        Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())),
                pathResolver, compiler, platformFlags, ruleFlags, getCompileOutputPath(target, name),
                source.getPath(), source.getType(), cxxPlatform.getDebugPathSanitizer());
        resolver.addToIndex(result);
        return result;
    }

    @VisibleForTesting
    CxxPreprocessAndCompile requireCompileBuildRule(BuildRuleResolver resolver, String name, CxxSource source,
            PicType pic) {

        BuildTarget target = createCompileBuildTarget(name, pic);
        Optional<CxxPreprocessAndCompile> existingRule = resolver.getRuleOptionalWithType(target,
                CxxPreprocessAndCompile.class);
        if (existingRule.isPresent()) {
            return existingRule.get();
        }

        return createCompileBuildRule(resolver, name, source, pic);
    }

    private ImmutableSortedSet<BuildRule> computeSourcePreprocessorAndToolDeps(Optional<Tool> toolOptional,
            CxxSource source) {

        ImmutableCollection<BuildRule> toolInputs = toolOptional.isPresent()
                ? toolOptional.get().getDeps(pathResolver)
                : ImmutableSet.<BuildRule>of();

        return ImmutableSortedSet.<BuildRule>naturalOrder()
                // Add dependencies on any build rules used to create the preprocessor.
                .addAll(toolInputs)
                // If a build rule generates our input source, add that as a dependency.
                .addAll(pathResolver.filterBuildRuleInputs(source.getPath()))
                // Add in all preprocessor deps.
                .addAll(preprocessDeps.get()).build();
    }

    private ImmutableList<String> computePlatformPreprocessorFlags(PicType pic, CxxSource.Type type) {
        return ImmutableList.<String>builder()
                // If we're using pic, add in the appropriate flag.
                .addAll(pic.getFlags())
                // Add in platform specific preprocessor flags.
                .addAll(CxxSourceTypes.getPlatformPreprocessFlags(cxxPlatform, type))
                // Add in the platform specific compiler flags.
                .addAll(getPlatformCompileFlags(CxxSourceTypes.getPreprocessorOutputType(type))).build();
    }

    private ImmutableList<String> computePlatformCompilerFlags(PicType pic, CxxSource source) {
        // Build up the list of compiler flags.
        return ImmutableList.<String>builder()
                // If we're using pic, add in the appropriate flag.
                .addAll(pic.getFlags())
                // Add in the platform specific compiler flags.
                .addAll(getPlatformCompileFlags(CxxSourceTypes.getPreprocessorOutputType(source.getType())))
                .build();
    }

    private ImmutableList<String> computeRulePreprocessorFlags(CxxSource.Type type,
            ImmutableList<String> sourceFlags) {
        return ImmutableList.<String>builder()
                // Add custom preprocessor flags.
                .addAll(preprocessorFlags.getUnchecked(type))
                // Add custom compiler flags.
                .addAll(getRuleCompileFlags(CxxSourceTypes.getPreprocessorOutputType(type)))
                // Add custom per-file flags.
                .addAll(sourceFlags).build();
    }

    private ImmutableList<String> computeRuleCompilerFlags(CxxSource source) {
        return ImmutableList.<String>builder()
                // Add custom compiler flags.
                .addAll(getRuleCompileFlags(CxxSourceTypes.getPreprocessorOutputType(source.getType())))
                // Add custom per-file flags.
                .addAll(source.getFlags()).build();
    }

    public CxxInferCapture requireInferCaptureBuildRule(String name, CxxSource source, PicType pic,
            CxxInferTools inferTools) {
        BuildTarget target = createInferCaptureBuildTarget(name);

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

        return createInferCaptureBuildRule(target, name, source, pic, inferTools);
    }

    public CxxInferCapture createInferCaptureBuildRule(BuildTarget target, String name, CxxSource source,
            PicType pic, CxxInferTools inferTools) {
        Preconditions.checkArgument(CxxSourceTypes.isPreprocessableType(source.getType()));

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

        CxxInferCapture result = new CxxInferCapture(
                params.copyWithChanges(target,
                        Suppliers.ofInstance(computeSourcePreprocessorAndToolDeps(Optional.<Tool>absent(), source)),
                        Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())),
                pathResolver, Optional.of(CxxSourceTypes.getPlatformPreprocessFlags(cxxPlatform, source.getType())),
                Optional.of(preprocessorFlags.getUnchecked(source.getType())),
                Optional.of(computePlatformCompilerFlags(pic, source)),
                Optional.of(computeRuleCompilerFlags(source)), source.getPath(), source.getType(),
                getCompileOutputPath(target, name), includeRoots.get(), systemIncludeRoots.get(), headerMaps.get(),
                frameworks.get(), CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, pathResolver),
                prefixHeader, inferTools, cxxPlatform.getDebugPathSanitizer());
        resolver.addToIndex(result);
        return result;
    }

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

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

        BuildTarget target = createCompileBuildTarget(name, pic);
        Compiler compiler = getCompiler(source.getType());

        LOG.verbose("Creating preprocess and compile %s for %s", target, source);

        // Build the CxxCompile rule and add it to our sorted set of build rules.
        CxxPreprocessAndCompile result = CxxPreprocessAndCompile.preprocessAndCompile(
                params.copyWithChanges(target,
                        Suppliers.ofInstance(
                                computeSourcePreprocessorAndToolDeps(Optional.of((Tool) compiler), source)),
                        Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())),
                pathResolver, preprocessAndCompilePreprocessorDelegate.getUnchecked(source.getType()), compiler,
                computePlatformCompilerFlags(pic, source), computeRuleCompilerFlags(source),
                getCompileOutputPath(target, name), source.getPath(), source.getType(),
                cxxPlatform.getDebugPathSanitizer(), strategy);
        resolver.addToIndex(result);
        return result;
    }

    @VisibleForTesting
    CxxPreprocessAndCompile requirePreprocessAndCompileBuildRule(BuildRuleResolver resolver, String name,
            CxxSource source, PicType pic, CxxPreprocessMode strategy) {

        BuildTarget target = createCompileBuildTarget(name, pic);
        Optional<CxxPreprocessAndCompile> existingRule = resolver.getRuleOptionalWithType(target,
                CxxPreprocessAndCompile.class);
        if (existingRule.isPresent()) {
            return existingRule.get();
        }

        return createPreprocessAndCompileBuildRule(resolver, name, source, pic, strategy);
    }

    public ImmutableSet<CxxInferCapture> createInferCaptureBuildRules(ImmutableMap<String, CxxSource> sources,
            PicType pic, CxxInferTools inferTools, 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, pic, inferTools);
            objects.add(rule);
        }

        return objects.build();
    }

    private ImmutableMap<CxxPreprocessAndCompile, SourcePath> requirePreprocessAndCompileRules(
            BuildRuleResolver resolver, CxxPreprocessMode strategy, ImmutableMap<String, CxxSource> sources,
            PicType pic) {

        ImmutableList.Builder<CxxPreprocessAndCompile> objects = ImmutableList.builder();

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

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

            switch (strategy) {

            case PIPED:
            case COMBINED: {
                CxxPreprocessAndCompile rule;

                // 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())) {
                    rule = requirePreprocessAndCompileBuildRule(resolver, name, source, pic, strategy);
                } else {
                    rule = requireCompileBuildRule(resolver, name, source, pic);
                }

                objects.add(rule);
                break;
            }

            case SEPARATE: {

                // If this is a preprocessable source, first create the preprocess build rule and
                // update the source and name to represent its compilable output.
                if (CxxSourceTypes.isPreprocessableType(source.getType())) {
                    CxxPreprocessAndCompile rule = requirePreprocessBuildRule(resolver, name, source, pic);
                    source = CxxSource.copyOf(source)
                            .withType(CxxSourceTypes.getPreprocessorOutputType(source.getType()))
                            .withPath(new BuildTargetSourcePath(rule.getBuildTarget()));
                }

                // Now build the compile build rule.
                CxxPreprocessAndCompile rule = requireCompileBuildRule(resolver, name, source, pic);
                objects.add(rule);

                break;
            }

            // $CASES-OMITTED$
            default:
                throw new IllegalStateException();
            }
        }

        return FluentIterable.from(objects.build()).toMap(new Function<CxxPreprocessAndCompile, SourcePath>() {
            @Override
            public SourcePath apply(CxxPreprocessAndCompile input) {
                return new BuildTargetSourcePath(input.getBuildTarget());
            }
        });
    }

    public static ImmutableMap<CxxPreprocessAndCompile, SourcePath> requirePreprocessAndCompileRules(
            BuildRuleParams params, BuildRuleResolver resolver, SourcePathResolver pathResolver,
            CxxPlatform cxxPlatform, ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput,
            ImmutableList<String> compilerFlags, Optional<SourcePath> prefixHeader, CxxPreprocessMode strategy,
            ImmutableMap<String, CxxSource> sources, PicType pic) {
        CxxSourceRuleFactory factory = new CxxSourceRuleFactory(params, resolver, pathResolver, cxxPlatform,
                cxxPreprocessorInput, compilerFlags, prefixHeader);
        return factory.requirePreprocessAndCompileRules(resolver, strategy, sources, pic);
    }

    public enum PicType {

        // Generate position-independent code (e.g. for use in shared libraries).
        PIC("-fPIC"),

        // Generate position-dependent code.
        PDC;

        private final ImmutableList<String> flags;

        PicType(String... flags) {
            this.flags = ImmutableList.copyOf(flags);
        }

        public ImmutableList<String> getFlags() {
            return flags;
        }

    }

    @Value.Immutable
    @BuckStyleImmutable
    interface AbstractPreprocessAndCompilePreprocessorDelegateKey {

        @Value.Parameter
        CxxSource.Type getSourceType();

        @Value.Parameter
        PicType getPicType();

        @Value.Parameter
        ImmutableList<String> getSourceFlags();

    }

    private abstract class PreprocessorDelegateCacheLoader<T> extends CacheLoader<T, PreprocessorDelegate> {

        @Override
        public PreprocessorDelegate load(T key) throws Exception {
            return new PreprocessorDelegate(pathResolver, cxxPlatform.getDebugPathSanitizer(),
                    params.getProjectFilesystem().getRootPath(),
                    CxxSourceTypes.getPreprocessor(cxxPlatform, getSourceType(key)),
                    getPlatformPreprocessorFlags(key), getRulePreprocessorFlags(key), includeRoots.get(),
                    systemIncludeRoots.get(), headerMaps.get(), frameworks.get(),
                    CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, pathResolver), prefixHeader,
                    includes.get());
        }

        abstract CxxSource.Type getSourceType(T key);

        abstract ImmutableList<String> getPlatformPreprocessorFlags(T key);

        abstract ImmutableList<String> getRulePreprocessorFlags(T key);

    }

    private class PreprocessAndCompileCacheLoader extends PreprocessorDelegateCacheLoader<CxxSource.Type> {

        @Override
        AbstractCxxSource.Type getSourceType(CxxSource.Type key) {
            return key;
        }

        @Override
        ImmutableList<String> getPlatformPreprocessorFlags(CxxSource.Type key) {
            return CxxSourceTypes.getPlatformPreprocessFlags(cxxPlatform, getSourceType(key));
        }

        @Override
        ImmutableList<String> getRulePreprocessorFlags(CxxSource.Type key) {
            return preprocessorFlags.getUnchecked(getSourceType(key));
        }

    }

    private class PreprocessPreprocessorCacheLoader
            extends PreprocessorDelegateCacheLoader<PreprocessAndCompilePreprocessorDelegateKey> {

        @Override
        AbstractCxxSource.Type getSourceType(PreprocessAndCompilePreprocessorDelegateKey key) {
            return key.getSourceType();
        }

        @Override
        ImmutableList<String> getPlatformPreprocessorFlags(PreprocessAndCompilePreprocessorDelegateKey key) {
            return computePlatformPreprocessorFlags(key.getPicType(), key.getSourceType());
        }

        @Override
        ImmutableList<String> getRulePreprocessorFlags(PreprocessAndCompilePreprocessorDelegateKey key) {
            return computeRulePreprocessorFlags(key.getSourceType(), key.getSourceFlags());
        }

    }

}