Java tutorial
/* * 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()); } } } }