/* * 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.InternalFlavor; 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.ExplicitBuildTargetSourcePath; 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 java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import javax.annotation.Nonnull; import org.immutables.value.Value; @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 = InternalFlavor.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. * * <p>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() .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(getPreprocessDeps()), Suppliers.ofInstance(ImmutableSortedSet.of())); DependencyAggregation rule = new DependencyAggregation(params); 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 = new ArrayList<>(); 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( InternalFlavor.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( InternalFlavor.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(agallagher): 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() .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(depsBuilder.build()), Suppliers.ofInstance(ImmutableSortedSet.of())), compilerDelegate, getCompileOutputPath(target, name), source.getPath(), source.getType(), getSanitizerForSourceType(source.getType()), 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() .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(depsBuilder.build()), Suppliers.ofInstance(ImmutableSortedSet.of())), 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, source.getType())) { throw new HumanReadableException( "Precompiled header was requested for rule \"" + this.getParams().getBuildTarget().toString() + "\", but PCH's are not possible under " + "the current environment (preprocessor/compiler, source file's language, " + "and/or 'cxx.pch_enabled' option)."); } Optional<CxxPrecompiledHeader> precompiledHeaderRule = Optional.empty(); if (canUsePrecompiledHeaders(getCxxBuckConfig(), preprocessor, source.getType()) && (getPrefixHeader().isPresent() || getPrecompiledHeader().isPresent())) { precompiledHeaderRule = Optional.of( requirePrecompiledHeaderBuildRule( preprocessorDelegateValue, source.getType(), source.getFlags())); depsBuilder.add(precompiledHeaderRule.get()); 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( precompiledHeaderRule.get().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() .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(depsBuilder.build()), Suppliers.ofInstance(ImmutableSortedSet.of())), preprocessorDelegate, compilerDelegate, getCompileOutputPath(target, name), source.getPath(), source.getType(), precompiledHeaderRule, getSanitizerForSourceType(source.getType()), 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. * * <p>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>'}. * * <p>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. * * <p>The {@code sourceType} and {@code sourceFlags} come from one of the source in the rule which * is using the PCH. This is so we can obtain certain flags (language options and such) so the PCH * is compatible with the rule requesting it. * * @param preprocessorDelegateCacheValue * @param sourceType * @param sourceFlags */ private CxxPrecompiledHeader requirePrecompiledHeaderBuildRule( PreprocessorDelegateCacheValue preprocessorDelegateCacheValue, CxxSource.Type sourceType, ImmutableList<String> sourceFlags) { // This method is called only if one of these is present; guarantee that for the if/else below. Preconditions.checkState(getPrefixHeader().isPresent() ^ getPrecompiledHeader().isPresent()); return getPrefixHeader().isPresent() ? buildPrecompiledHeaderFromPrefixHeader( preprocessorDelegateCacheValue, sourceType, sourceFlags, getPrefixHeader().get()) : buildPrecompiledHeaderFromTemplateRule( preprocessorDelegateCacheValue, sourceType, sourceFlags, getPrecompiledHeader().get()); } private CxxPrecompiledHeader buildPrecompiledHeaderFromPrefixHeader( PreprocessorDelegateCacheValue preprocessorDelegateCacheValue, CxxSource.Type sourceType, ImmutableList<String> sourceFlags, SourcePath headerPath) { DepsBuilder depsBuilder = new DepsBuilder(getRuleFinder()); // We need the preprocessor deps for this rule, for its prefix header. depsBuilder.add(preprocessorDelegateCacheValue.getPreprocessorDelegate()); depsBuilder.add(requireAggregatedPreprocessDepsRule()); CxxToolFlags compilerFlags = computeCompilerFlags(sourceType, sourceFlags); // 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(), InternalFlavor.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). */ private CxxPrecompiledHeader buildPrecompiledHeaderFromTemplateRule( PreprocessorDelegateCacheValue preprocessorDelegateCacheValue, CxxSource.Type sourceType, ImmutableList<String> sourceFlags, SourcePath headerTargetPath) { DepsBuilder depsBuilder = new DepsBuilder(getRuleFinder()); PreprocessorDelegate preprocessorDelegateForCxxRule = preprocessorDelegateCacheValue.getPreprocessorDelegate(); Preprocessor preprocessor = preprocessorDelegateForCxxRule.getPreprocessor(); BuildTarget pchTemplateTarget = ((BuildTargetSourcePath) headerTargetPath).getTarget(); Optional<CxxPrecompiledHeaderTemplate> pchTemplateRuleOpt = getResolver() .getRuleOptionalWithType(pchTemplateTarget, CxxPrecompiledHeaderTemplate.class); Preconditions.checkState(pchTemplateRuleOpt.isPresent()); CxxPrecompiledHeaderTemplate pchTemplate = pchTemplateRuleOpt.get(); // Build compiler flags, taking from the source rule, but leaving out its deps. // We just need the flags pertaining to PCH compatibility: language, PIC, macros, etc. // and nothing related to the deps of this particular rule (hence 'getNonIncludePathFlags'). CxxToolFlags compilerFlags = CxxToolFlags.concat( preprocessorDelegateForCxxRule.getNonIncludePathFlags(/* no pch */ Optional.empty()), computeCompilerFlags(sourceType, sourceFlags)); // Now build a new pp-delegate specially for this PCH rule. PreprocessorDelegate preprocessorDelegate = pchTemplate.buildPreprocessorDelegate(getCxxPlatform(), preprocessor, compilerFlags); // 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); for (BuildRule rule : pchTemplate.getBuildDeps()) { depsBuilder.add(rule); } depsBuilder.add(pchTemplate.requireAggregatedDepsRule(getCxxPlatform())); depsBuilder.add(preprocessorDelegate); return buildPrecompiledHeader( preprocessorDelegate, sourceType, compilerFlags, pchTemplate.sourcePath, depsBuilder, pchTemplateTarget.getUnflavoredBuildTarget(), ImmutableSortedSet.of( getCxxPlatform().getFlavor(), InternalFlavor.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. */ 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() .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(depsBuilder.build()), Suppliers.ofInstance(ImmutableSortedSet.of())); CxxPrecompiledHeader rule = new CxxPrecompiledHeader( params, output, preprocessorDelegate, compilerDelegate, compilerFlags, headerPath, sourceType, getCxxPlatform().getCompilerDebugPathSanitizer()); 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(), CxxPreprocessAndCompile::getSourcePathToOutput)); } 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"); ExplicitBuildTargetSourcePath path = new ExplicitBuildTargetSourcePath( 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? */ @VisibleForTesting boolean canUsePrecompiledHeaders( CxxBuckConfig cxxBuckConfig, Preprocessor preprocessor, CxxSource.Type sourceType) { return cxxBuckConfig.isPCHEnabled() && preprocessor.supportsPrecompiledHeaders() && sourceType.getPrecompiledHeaderLanguage().isPresent(); } private DebugPathSanitizer getSanitizerForSourceType(CxxSource.Type type) { return type.isAssembly() ? getCxxPlatform().getAssemblerDebugPathSanitizer() : getCxxPlatform().getCompilerDebugPathSanitizer(); } 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()); } } } }