| /* |
| * 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.thrift; |
| |
| import com.facebook.buck.cxx.CxxBuckConfig; |
| import com.facebook.buck.cxx.CxxCompilables; |
| import com.facebook.buck.cxx.CxxLibraryDescription; |
| import com.facebook.buck.cxx.CxxPlatform; |
| import com.facebook.buck.model.BuildTarget; |
| import com.facebook.buck.model.BuildTargets; |
| import com.facebook.buck.model.Flavor; |
| import com.facebook.buck.model.FlavorDomain; |
| 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.BuildTargetSourcePath; |
| import com.facebook.buck.rules.SourcePath; |
| import com.facebook.buck.rules.coercer.Either; |
| import com.facebook.buck.rules.coercer.SourceWithFlags; |
| import com.facebook.buck.util.HumanReadableException; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| 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.io.Files; |
| |
| import java.nio.file.Path; |
| |
| public class ThriftCxxEnhancer implements ThriftLanguageSpecificEnhancer { |
| |
| private static final Flavor CPP_FLAVOR = ImmutableFlavor.of("cpp"); |
| private static final Flavor CPP2_FLAVOR = ImmutableFlavor.of("cpp2"); |
| |
| private final ThriftBuckConfig thriftBuckConfig; |
| private final CxxLibraryDescription cxxLibraryDescription; |
| private final boolean cpp2; |
| |
| public ThriftCxxEnhancer( |
| ThriftBuckConfig thriftBuckConfig, |
| CxxBuckConfig cxxBuckConfig, |
| FlavorDomain<CxxPlatform> cxxPlatforms, |
| boolean cpp2) { |
| this.thriftBuckConfig = thriftBuckConfig; |
| this.cxxLibraryDescription = new CxxLibraryDescription(cxxBuckConfig, cxxPlatforms); |
| this.cpp2 = cpp2; |
| } |
| |
| @Override |
| public String getLanguage() { |
| return cpp2 ? "cpp2" : "cpp"; |
| } |
| |
| @Override |
| public Flavor getFlavor() { |
| return cpp2 ? CPP2_FLAVOR : CPP_FLAVOR; |
| } |
| |
| // Return the gen-dir relative sources for the given services and options. |
| @VisibleForTesting |
| protected ImmutableList<String> getGeneratedThriftSources( |
| ImmutableSet<String> options, |
| String name, |
| ImmutableList<String> services) { |
| |
| final String base = Files.getNameWithoutExtension(name); |
| final boolean bootstrap = options.contains("bootstrap"); |
| final boolean layouts = options.contains("frozen"); |
| final boolean templates = cpp2 || options.contains("templates"); |
| final boolean perfhash = !cpp2 && options.contains("perfhash"); |
| |
| ImmutableList.Builder<String> sources = ImmutableList.builder(); |
| |
| sources.add(base + "_constants.h"); |
| sources.add(base + "_constants.cpp"); |
| |
| sources.add(base + "_types.h"); |
| sources.add(base + "_types.cpp"); |
| |
| if (templates) { |
| sources.add(base + "_types.tcc"); |
| } |
| |
| if (layouts) { |
| sources.add(base + "_layouts.h"); |
| sources.add(base + "_layouts.cpp"); |
| } |
| |
| if (!bootstrap && !cpp2) { |
| sources.add(base + "_reflection.h"); |
| sources.add(base + "_reflection.cpp"); |
| } |
| |
| for (String service : services) { |
| |
| sources.add(service + ".h"); |
| sources.add(service + ".cpp"); |
| |
| if (templates) { |
| sources.add(service + ".tcc"); |
| } |
| |
| if (perfhash) { |
| sources.add(service + "_gperf.tcc"); |
| } |
| |
| } |
| |
| return sources.build(); |
| } |
| |
| // Find all the generated sources and headers generated by these thrift sources. |
| private CxxHeadersAndSources getThriftHeaderSourceSpec( |
| BuildRuleParams params, |
| ThriftConstructorArg args, |
| ImmutableMap<String, ThriftSource> sources) { |
| |
| ImmutableSet<String> options = |
| (cpp2 ? args.cpp2Options : args.cppOptions).or(ImmutableSet.<String>of()); |
| |
| ImmutableMap.Builder<String, SourceWithFlags> cxxSourcesBuilder = ImmutableMap.builder(); |
| ImmutableMap.Builder<String, SourcePath> headersBuilder = ImmutableMap.builder(); |
| |
| for (ImmutableMap.Entry<String, ThriftSource> ent : sources.entrySet()) { |
| final String thriftName = ent.getKey(); |
| final ThriftSource source = ent.getValue(); |
| final Path outputDir = source.getOutputDir(); |
| |
| for (String partialName : getGeneratedThriftSources( |
| options, |
| thriftName, |
| source.getServices())) { |
| |
| String name = String.format("gen-%s/%s", getLanguage(), partialName); |
| String extension = Files.getFileExtension(name); |
| |
| if (CxxCompilables.SOURCE_EXTENSIONS.contains(extension)) { |
| cxxSourcesBuilder.put( |
| name, |
| SourceWithFlags.of( |
| new BuildTargetSourcePath( |
| source.getCompileRule().getProjectFilesystem(), |
| source.getCompileRule().getBuildTarget(), |
| outputDir.resolve(name)))); |
| } else if (CxxCompilables.HEADER_EXTENSIONS.contains(extension)) { |
| headersBuilder.put( |
| name, |
| new BuildTargetSourcePath( |
| source.getCompileRule().getProjectFilesystem(), |
| source.getCompileRule().getBuildTarget(), |
| outputDir.resolve(name))); |
| } else { |
| throw new HumanReadableException(String.format( |
| "%s: unexpected extension for \"%s\"", |
| params.getBuildTarget(), |
| name)); |
| } |
| } |
| } |
| |
| return new CxxHeadersAndSources( |
| headersBuilder.build(), |
| cxxSourcesBuilder.build()); |
| } |
| |
| @Override |
| public BuildRule createBuildRule( |
| BuildRuleParams params, |
| BuildRuleResolver resolver, |
| ThriftConstructorArg args, |
| ImmutableMap<String, ThriftSource> sources, |
| ImmutableSortedSet<BuildRule> deps) { |
| |
| // Grab all the sources and headers generated from the passed in thrift sources. |
| CxxHeadersAndSources spec = getThriftHeaderSourceSpec(params, args, sources); |
| |
| // Add all the passed in language-specific thrift deps, and any C/C++ specific deps |
| // passed in via the constructor arg. |
| ImmutableSortedSet<BuildRule> allDeps = |
| ImmutableSortedSet.<BuildRule>naturalOrder() |
| .addAll(deps) |
| .addAll( |
| resolver.getAllRules( |
| (cpp2 ? args.cpp2Deps : args.cppDeps).or(ImmutableSortedSet.<BuildTarget>of()))) |
| .build(); |
| |
| // Create language specific build params by using the deps we formed above. |
| BuildRuleParams langParams = params.copyWithDeps( |
| Suppliers.ofInstance(allDeps), |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())); |
| |
| // Construct the C/C++ library description argument to pass to the |
| CxxLibraryDescription.Arg langArgs = cxxLibraryDescription.createEmptyConstructorArg(); |
| langArgs.srcs = |
| Optional.of( |
| Either.<ImmutableList<SourceWithFlags>, ImmutableMap<String, SourceWithFlags>>ofRight( |
| spec.getSources())); |
| langArgs.exportedHeaders = |
| Optional.of( |
| Either.<ImmutableList<SourcePath>, ImmutableMap<String, SourcePath>>ofRight( |
| spec.getHeaders())); |
| |
| return cxxLibraryDescription.createBuildRule(langParams, resolver, langArgs); |
| } |
| |
| private ImmutableSet<BuildTarget> getImplicitDepsFromOptions( |
| BuildTarget target, |
| ImmutableSet<String> options) { |
| |
| ImmutableSet.Builder<BuildTarget> implicitDeps = ImmutableSet.builder(); |
| |
| if (!options.contains("bootstrap")) { |
| if (cpp2) { |
| implicitDeps.add(thriftBuckConfig.getCpp2Dep()); |
| } else { |
| implicitDeps.add(thriftBuckConfig.getCppDep()); |
| } |
| implicitDeps.add(thriftBuckConfig.getCppReflectionDep()); |
| } |
| |
| if (options.contains("frozen2")) { |
| implicitDeps.add(thriftBuckConfig.getCppFrozenDep()); |
| } |
| |
| if (options.contains("json")) { |
| implicitDeps.add(thriftBuckConfig.getCppJsonDep()); |
| } |
| |
| if (cpp2 && options.contains("compatibility")) { |
| implicitDeps.add(thriftBuckConfig.getCppDep()); |
| BuildTarget cppTarget = BuildTargets.createFlavoredBuildTarget( |
| target.getUnflavoredBuildTarget(), |
| CPP_FLAVOR); |
| implicitDeps.add(cppTarget); |
| } |
| |
| if (!cpp2 && options.contains("cob_style")) { |
| implicitDeps.add(thriftBuckConfig.getCppAyncDep()); |
| } |
| |
| return implicitDeps.build(); |
| } |
| |
| @Override |
| public ImmutableSet<BuildTarget> getImplicitDepsForTargetFromConstructorArg( |
| BuildTarget target, |
| ThriftConstructorArg arg) { |
| Optional<ImmutableSet<String>> options = cpp2 ? arg.cpp2Options : arg.cppOptions; |
| return getImplicitDepsFromOptions(target, options.or(ImmutableSet.<String>of())); |
| } |
| |
| @Override |
| public ImmutableSet<String> getOptions( |
| BuildTarget target, |
| ThriftConstructorArg args) { |
| return ImmutableSet.<String>builder() |
| .add(String.format("include_prefix=%s", target.getBasePath())) |
| .addAll((cpp2 ? args.cpp2Options : args.cppOptions).or(ImmutableSet.<String>of())) |
| .build(); |
| } |
| |
| private static class CxxHeadersAndSources { |
| |
| private final ImmutableMap<String, SourcePath> headers; |
| private final ImmutableMap<String, SourceWithFlags> sources; |
| |
| public CxxHeadersAndSources( |
| ImmutableMap<String, SourcePath> headers, |
| ImmutableMap<String, SourceWithFlags> sources) { |
| this.headers = headers; |
| this.sources = sources; |
| } |
| |
| public ImmutableMap<String, SourcePath> getHeaders() { |
| return headers; |
| } |
| |
| public ImmutableMap<String, SourceWithFlags> getSources() { |
| return sources; |
| } |
| |
| } |
| |
| } |