/* * Copyright 2014-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.graph.AbstractBreadthFirstThrowingTraversal; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.FlavorConvertible; import com.facebook.buck.model.InternalFlavor; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.coercer.FrameworkPath; import com.google.common.base.CaseFormat; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import java.nio.file.Path; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.Nonnull; import org.immutables.value.Value; public class CxxPreprocessables { private CxxPreprocessables() {} public enum HeaderMode implements FlavorConvertible { /** Creates the tree of symbolic links of headers. */ SYMLINK_TREE_ONLY, /** Creates the header map that references the headers directly in the source tree. */ HEADER_MAP_ONLY, /** * Creates the tree of symbolic links of headers and creates the header map that references the * symbolic links to the headers. */ SYMLINK_TREE_WITH_HEADER_MAP, ; private final Flavor flavor; HeaderMode() { this.flavor = InternalFlavor.of( String.format( "%s-%s", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, getClass().getSimpleName()), CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, toString()))); } @Override public Flavor getFlavor() { return flavor; } } public enum IncludeType { /** Headers should be included with `-I`. */ LOCAL { @Override public Iterable<String> includeArgs(Preprocessor pp, Iterable<String> includeRoots) { return pp.localIncludeArgs(includeRoots); } }, /** Headers should be included with `-isystem`. */ SYSTEM { @Override public Iterable<String> includeArgs(Preprocessor pp, Iterable<String> includeRoots) { return pp.systemIncludeArgs(includeRoots); } }, /** Headers should be included with `-iquote`. */ IQUOTE { @Override public Iterable<String> includeArgs(Preprocessor pp, Iterable<String> includeRoots) { return pp.quoteIncludeArgs(includeRoots); } }, ; public abstract Iterable<String> includeArgs(Preprocessor pp, Iterable<String> includeRoots); } /** * Resolve the map of name to {@link SourcePath} to a map of full header name to {@link * SourcePath}. */ public static ImmutableMap<Path, SourcePath> resolveHeaderMap( Path basePath, ImmutableMap<String, SourcePath> headers) { ImmutableMap.Builder<Path, SourcePath> headerMap = ImmutableMap.builder(); // Resolve the "names" of the headers to actual paths by prepending the base path // specified by the build target. for (ImmutableMap.Entry<String, SourcePath> ent : headers.entrySet()) { Path path = basePath.resolve(ent.getKey()); headerMap.put(path, ent.getValue()); } return headerMap.build(); } /** * Find and return the {@link CxxPreprocessorInput} objects from {@link CxxPreprocessorDep} found * while traversing the dependencies starting from the {@link BuildRule} objects given. */ public static Collection<CxxPreprocessorInput> getTransitiveCxxPreprocessorInput( final CxxPlatform cxxPlatform, Iterable<? extends BuildRule> inputs, final Predicate<Object> traverse) throws NoSuchBuildTargetException { // We don't really care about the order we get back here, since headers shouldn't // conflict. However, we want something that's deterministic, so sort by build // target. final Map<BuildTarget, CxxPreprocessorInput> deps = Maps.newLinkedHashMap(); // Build up the map of all C/C++ preprocessable dependencies. new AbstractBreadthFirstThrowingTraversal<BuildRule, NoSuchBuildTargetException>(inputs) { @Override public ImmutableSet<BuildRule> visit(BuildRule rule) throws NoSuchBuildTargetException { if (rule instanceof CxxPreprocessorDep) { CxxPreprocessorDep dep = (CxxPreprocessorDep) rule; deps.putAll(dep.getTransitiveCxxPreprocessorInput(cxxPlatform, HeaderVisibility.PUBLIC)); return ImmutableSet.of(); } return traverse.apply(rule) ? rule.getBuildDeps() : ImmutableSet.of(); } }.start(); // Grab the cxx preprocessor inputs and return them. return deps.values(); } public static Collection<CxxPreprocessorInput> getTransitiveCxxPreprocessorInput( final CxxPlatform cxxPlatform, Iterable<? extends BuildRule> inputs) throws NoSuchBuildTargetException { return getTransitiveCxxPreprocessorInput(cxxPlatform, inputs, x -> true); } /** * Build the {@link HeaderSymlinkTree} rule using the original build params from a target node. In * particular, make sure to drop all dependencies from the original build rule params, as these * are modeled via {@link CxxPreprocessAndCompile}. */ public static HeaderSymlinkTree createHeaderSymlinkTreeBuildRule( BuildTarget target, ProjectFilesystem filesystem, Path root, ImmutableMap<Path, SourcePath> links, HeaderMode headerMode, SourcePathRuleFinder ruleFinder) { switch (headerMode) { case SYMLINK_TREE_WITH_HEADER_MAP: return HeaderSymlinkTreeWithHeaderMap.create(target, filesystem, root, links, ruleFinder); case HEADER_MAP_ONLY: return new DirectHeaderMap(target, filesystem, root, links, ruleFinder); default: case SYMLINK_TREE_ONLY: return new HeaderSymlinkTree(target, filesystem, root, links, ruleFinder); } } /** * @return adds a the header {@link com.facebook.buck.rules.SymlinkTree} for the given rule to the * {@link CxxPreprocessorInput}. */ public static CxxPreprocessorInput.Builder addHeaderSymlinkTree( CxxPreprocessorInput.Builder builder, BuildTarget target, BuildRuleResolver ruleResolver, CxxPlatform platform, HeaderVisibility headerVisibility, IncludeType includeType) throws NoSuchBuildTargetException { BuildRule rule = ruleResolver.requireRule( BuildTarget.builder(target) .addFlavors( platform.getFlavor(), CxxDescriptionEnhancer.getHeaderSymlinkTreeFlavor(headerVisibility)) .build()); Preconditions.checkState( rule instanceof HeaderSymlinkTree, "Attempt to add %s of type %s and class %s to %s", rule.getFullyQualifiedName(), rule.getType(), rule.getClass().getName(), target); HeaderSymlinkTree symlinkTree = (HeaderSymlinkTree) rule; builder.addIncludes(CxxSymlinkTreeHeaders.from(symlinkTree, includeType)); return builder; } /** Builds a {@link CxxPreprocessorInput} for a rule. */ public static CxxPreprocessorInput getCxxPreprocessorInput( BuildRuleParams params, BuildRuleResolver ruleResolver, boolean hasHeaderSymlinkTree, CxxPlatform platform, HeaderVisibility headerVisibility, IncludeType includeType, Multimap<CxxSource.Type, String> exportedPreprocessorFlags, Iterable<FrameworkPath> frameworks) throws NoSuchBuildTargetException { CxxPreprocessorInput.Builder builder = CxxPreprocessorInput.builder(); if (hasHeaderSymlinkTree) { addHeaderSymlinkTree( builder, params.getBuildTarget(), ruleResolver, platform, headerVisibility, includeType); } return builder .putAllPreprocessorFlags(exportedPreprocessorFlags) .addAllFrameworks(frameworks) .build(); } public static LoadingCache< CxxPreprocessorInputCacheKey, ImmutableMap<BuildTarget, CxxPreprocessorInput>> getTransitiveCxxPreprocessorInputCache(final CxxPreprocessorDep preprocessorDep) { return CacheBuilder.newBuilder() .build( new CacheLoader< CxxPreprocessorInputCacheKey, ImmutableMap<BuildTarget, CxxPreprocessorInput>>() { @Override public ImmutableMap<BuildTarget, CxxPreprocessorInput> load( @Nonnull CxxPreprocessorInputCacheKey key) throws Exception { Map<BuildTarget, CxxPreprocessorInput> builder = new LinkedHashMap<>(); builder.put( preprocessorDep.getBuildTarget(), preprocessorDep.getCxxPreprocessorInput( key.getPlatform(), key.getVisibility())); for (CxxPreprocessorDep dep : preprocessorDep.getCxxPreprocessorDeps(key.getPlatform())) { builder.putAll( dep.getTransitiveCxxPreprocessorInput( key.getPlatform(), key.getVisibility())); } return ImmutableMap.copyOf(builder); } }); } @Value.Immutable public abstract static class CxxPreprocessorInputCacheKey implements Comparable<CxxPreprocessorInputCacheKey> { @Value.Parameter public abstract CxxPlatform getPlatform(); @Value.Parameter public abstract HeaderVisibility getVisibility(); @Override public int compareTo(@Nonnull CxxPreprocessorInputCacheKey o) { return ComparisonChain.start() .compare(getPlatform().getFlavor(), o.getPlatform().getFlavor()) .compare(getVisibility(), o.getVisibility()) .result(); } } }