| /* |
| * 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.AbstractBreadthFirstTraversal; |
| 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.BuildRuleType; |
| import com.facebook.buck.rules.BuildRules; |
| import com.facebook.buck.rules.BuildTargetSourcePath; |
| import com.facebook.buck.rules.ImmutableBuildRuleType; |
| import com.facebook.buck.rules.SourcePath; |
| import com.facebook.buck.rules.SourcePathResolver; |
| import com.facebook.buck.rules.SymlinkTree; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.base.Suppliers; |
| 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.Maps; |
| |
| import java.nio.file.Path; |
| import java.util.AbstractMap; |
| import java.util.Map; |
| |
| public class CxxPreprocessables { |
| |
| private CxxPreprocessables() {} |
| |
| private static final BuildRuleType HEADER_SYMLINK_TREE_TYPE = |
| ImmutableBuildRuleType.of("header_symlink_tree"); |
| |
| private static final BuildRuleType PREPROCESS_TYPE = ImmutableBuildRuleType.of("preprocess"); |
| |
| /** |
| * 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 CxxPreprocessorInput getTransitiveCxxPreprocessorInput( |
| final CxxPlatform cxxPlatform, |
| Iterable<? extends BuildRule> inputs, |
| final Predicate<Object> traverse) |
| throws CxxPreprocessorInput.ConflictingHeadersException { |
| |
| // 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.newTreeMap(); |
| |
| // Build up the map of all C/C++ preprocessable dependencies. |
| AbstractBreadthFirstTraversal<BuildRule> visitor = |
| new AbstractBreadthFirstTraversal<BuildRule>(inputs) { |
| @Override |
| public ImmutableSet<BuildRule> visit(BuildRule rule) { |
| if (rule instanceof CxxPreprocessorDep) { |
| CxxPreprocessorDep dep = (CxxPreprocessorDep) rule; |
| Preconditions.checkState(!deps.containsKey(rule.getBuildTarget())); |
| deps.put(rule.getBuildTarget(), dep.getCxxPreprocessorInput(cxxPlatform)); |
| } |
| return traverse.apply(rule) ? rule.getDeps() : ImmutableSet.<BuildRule>of(); |
| } |
| }; |
| visitor.start(); |
| |
| // Grab the cxx preprocessor inputs and return them. |
| return CxxPreprocessorInput.concat(deps.values()); |
| } |
| |
| public static CxxPreprocessorInput getTransitiveCxxPreprocessorInput( |
| final CxxPlatform cxxPlatform, |
| Iterable<? extends BuildRule> inputs) |
| throws CxxPreprocessorInput.ConflictingHeadersException { |
| return getTransitiveCxxPreprocessorInput( |
| cxxPlatform, |
| inputs, |
| Predicates.instanceOf(CxxPreprocessorDep.class)); |
| } |
| |
| /** |
| * Build the {@link SymlinkTree} 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 CxxCompile}. |
| */ |
| public static SymlinkTree createHeaderSymlinkTreeBuildRule( |
| SourcePathResolver resolver, |
| BuildTarget target, |
| BuildRuleParams params, |
| Path root, |
| ImmutableMap<Path, SourcePath> links) { |
| |
| return new SymlinkTree( |
| params.copyWithChanges( |
| HEADER_SYMLINK_TREE_TYPE, |
| target, |
| // Symlink trees never need to depend on anything. |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()), |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), |
| resolver, |
| root, |
| links); |
| } |
| |
| /** |
| * @return the preprocessed file name for the given source name. |
| */ |
| private static String getOutputName(CxxSource.Type type, String name) { |
| |
| String extension; |
| switch (type) { |
| case ASSEMBLER_WITH_CPP: |
| extension = "s"; |
| break; |
| case C: |
| extension = "i"; |
| break; |
| case CXX: |
| extension = "ii"; |
| break; |
| case OBJC: |
| extension = "mi"; |
| break; |
| case OBJCXX: |
| extension = "mii"; |
| break; |
| // $CASES-OMITTED$ |
| default: |
| throw new IllegalStateException(String.format("unexpected type: %s", type)); |
| } |
| |
| return name + "." + extension; |
| } |
| |
| /** |
| * @return a {@link BuildTarget} used for the rule that preprocesses the source by the given |
| * name and type. |
| */ |
| public static BuildTarget createPreprocessBuildTarget( |
| BuildTarget target, |
| Flavor platform, |
| CxxSource.Type type, |
| boolean pic, |
| String name) { |
| return BuildTarget |
| .builder(target) |
| .addFlavors(platform) |
| .addFlavors( |
| ImmutableFlavor.of( |
| String.format( |
| "preprocess-%s%s", |
| pic ? "pic-" : "", |
| getOutputName(type, name) |
| .replace('/', '-') |
| .replace('.', '-') |
| .replace('+', '-') |
| .replace(' ', '-')))) |
| .build(); |
| } |
| |
| /** |
| * @return the output path for an object file compiled from the source with the given name. |
| */ |
| public static Path getPreprocessOutputPath(BuildTarget target, CxxSource.Type type, String name) { |
| return BuildTargets.getBinPath(target, "%s").resolve(getOutputName(type, name)); |
| } |
| |
| /** |
| * Generate a build rule that preprocesses the given source. |
| * |
| * @return a pair of the output name and source. |
| */ |
| public static Map.Entry<String, CxxSource> createPreprocessBuildRule( |
| BuildRuleParams params, |
| BuildRuleResolver resolver, |
| CxxPlatform cxxPlatform, |
| CxxPreprocessorInput preprocessorInput, |
| boolean pic, |
| String name, |
| CxxSource source) { |
| |
| SourcePathResolver pathResolver = new SourcePathResolver(resolver); |
| |
| ImmutableSortedSet.Builder<BuildRule> dependencies = ImmutableSortedSet.naturalOrder(); |
| |
| // If a build rule generates our input source, add that as a dependency. |
| dependencies.addAll(pathResolver.filterBuildRuleInputs(ImmutableList.of(source.getPath()))); |
| |
| // Depend on the rule that generates the sources and headers we're compiling. |
| dependencies.addAll( |
| pathResolver.filterBuildRuleInputs( |
| ImmutableList.<SourcePath>builder() |
| .add(source.getPath()) |
| .addAll(preprocessorInput.getIncludes().getPrefixHeaders()) |
| .addAll(preprocessorInput.getIncludes().getNameToPathMap().values()) |
| .build())); |
| |
| // Also add in extra deps from the preprocessor input, such as the symlink tree |
| // rules. |
| dependencies.addAll( |
| BuildRules.toBuildRulesFor( |
| params.getBuildTarget(), |
| resolver, |
| preprocessorInput.getRules(), |
| false)); |
| |
| Tool preprocessor; |
| ImmutableList.Builder<String> args = ImmutableList.builder(); |
| CxxSource.Type outputType; |
| |
| // We explicitly identify our source rather then let the compiler guess based on the |
| // extension. |
| args.add("-x", source.getType().getLanguage()); |
| |
| // Pick the compiler to use. Basically, if we're dealing with C++ sources, use the C++ |
| // compiler, and the C compiler for everything. |
| switch (source.getType()) { |
| case ASSEMBLER_WITH_CPP: |
| preprocessor = cxxPlatform.getAspp(); |
| args.addAll(cxxPlatform.getAsppflags()); |
| args.addAll( |
| preprocessorInput.getPreprocessorFlags().get(CxxSource.Type.ASSEMBLER_WITH_CPP)); |
| outputType = CxxSource.Type.ASSEMBLER; |
| break; |
| case C: |
| preprocessor = cxxPlatform.getCpp(); |
| args.addAll(cxxPlatform.getCppflags()); |
| args.addAll(preprocessorInput.getPreprocessorFlags().get(CxxSource.Type.C)); |
| outputType = CxxSource.Type.C_CPP_OUTPUT; |
| break; |
| case CXX: |
| preprocessor = cxxPlatform.getCxxpp(); |
| args.addAll(cxxPlatform.getCxxppflags()); |
| args.addAll(preprocessorInput.getPreprocessorFlags().get(CxxSource.Type.CXX)); |
| outputType = CxxSource.Type.CXX_CPP_OUTPUT; |
| break; |
| case OBJC: |
| preprocessor = cxxPlatform.getCpp(); |
| args.addAll(cxxPlatform.getCppflags()); |
| args.addAll(preprocessorInput.getPreprocessorFlags().get(CxxSource.Type.OBJC)); |
| outputType = CxxSource.Type.OBJC_CPP_OUTPUT; |
| break; |
| case OBJCXX: |
| preprocessor = cxxPlatform.getCxxpp(); |
| args.addAll(cxxPlatform.getCxxppflags()); |
| args.addAll(preprocessorInput.getPreprocessorFlags().get(CxxSource.Type.OBJCXX)); |
| outputType = CxxSource.Type.OBJCXX_CPP_OUTPUT; |
| break; |
| // $CASES-OMITTED$ |
| default: |
| throw new IllegalStateException(String.format("unexpected type: %s", source.getType())); |
| } |
| |
| // Add dependencies on any build rules used to create the preprocessor. |
| dependencies.addAll(preprocessor.getBuildRules(pathResolver)); |
| |
| // If we're using pic, add in the appropriate flag. |
| if (pic) { |
| args.add("-fPIC"); |
| } |
| |
| // Add custom per-file flags. |
| args.addAll(source.getFlags()); |
| |
| // Build the CxxCompile rule and add it to our sorted set of build rules. |
| BuildTarget target = |
| createPreprocessBuildTarget( |
| params.getBuildTarget(), |
| cxxPlatform.getFlavor(), |
| source.getType(), |
| pic, |
| name); |
| CxxPreprocess cxxPreprocess = new CxxPreprocess( |
| params.copyWithChanges( |
| PREPROCESS_TYPE, |
| target, |
| Suppliers.ofInstance(dependencies.build()), |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), |
| pathResolver, |
| preprocessor, |
| args.build(), |
| getPreprocessOutputPath(target, source.getType(), name), |
| source.getPath(), |
| ImmutableList.copyOf(preprocessorInput.getIncludeRoots()), |
| ImmutableList.copyOf(preprocessorInput.getSystemIncludeRoots()), |
| ImmutableList.copyOf(preprocessorInput.getFrameworkRoots()), |
| preprocessorInput.getIncludes(), |
| cxxPlatform.getDebugPathSanitizer()); |
| resolver.addToIndex(cxxPreprocess); |
| |
| // Return the output name and source pair. |
| return new AbstractMap.SimpleEntry<String, CxxSource>( |
| name, |
| ImmutableCxxSource.of( |
| outputType, |
| new BuildTargetSourcePath( |
| cxxPreprocess.getProjectFilesystem(), |
| cxxPreprocess.getBuildTarget()), |
| source.getFlags())); |
| } |
| |
| /** |
| * Generate build rules which preprocess the given input sources. |
| */ |
| public static ImmutableMap<String, CxxSource> createPreprocessBuildRules( |
| BuildRuleParams params, |
| BuildRuleResolver resolver, |
| CxxPlatform config, |
| CxxPreprocessorInput cxxPreprocessorInput, |
| boolean pic, |
| ImmutableMap<String, CxxSource> sources) { |
| |
| ImmutableMap.Builder<String, CxxSource> preprocessedSources = ImmutableMap.builder(); |
| |
| for (Map.Entry<String, CxxSource> entry : sources.entrySet()) { |
| if (CxxSourceTypes.isPreprocessableType(entry.getValue().getType())) { |
| entry = CxxPreprocessables.createPreprocessBuildRule( |
| params, |
| resolver, |
| config, |
| cxxPreprocessorInput, |
| pic, |
| entry.getKey(), |
| entry.getValue()); |
| } |
| preprocessedSources.put(entry); |
| } |
| |
| return preprocessedSources.build(); |
| } |
| |
| } |