| /* |
| * 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.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.FlavorDomain; |
| import com.facebook.buck.model.FlavorDomainException; |
| import com.facebook.buck.model.Flavored; |
| import com.facebook.buck.model.HasBuildTarget; |
| 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.Description; |
| import com.facebook.buck.rules.ImmutableBuildRuleType; |
| import com.facebook.buck.rules.ImplicitDepsInferringDescription; |
| import com.facebook.buck.rules.SourcePath; |
| import com.facebook.buck.rules.SourcePathResolver; |
| import com.facebook.buck.rules.SymlinkTree; |
| import com.facebook.buck.util.HumanReadableException; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Suppliers; |
| import com.google.common.collect.FluentIterable; |
| 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.Iterables; |
| import com.google.common.collect.Lists; |
| |
| import java.nio.file.Path; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class ThriftLibraryDescription |
| implements |
| Description<ThriftConstructorArg>, |
| Flavored, |
| ImplicitDepsInferringDescription<ThriftConstructorArg> { |
| |
| public static final BuildRuleType TYPE = ImmutableBuildRuleType.of("thrift_library"); |
| private static final Flavor INCLUDE_SYMLINK_TREE_FLAVOR = |
| ImmutableFlavor.of("include_symlink_tree"); |
| private static final BuildRuleType INCLUDE_SYMLINK_TREE_TYPE = |
| ImmutableBuildRuleType.of("include_symlink_tree"); |
| |
| private final ThriftBuckConfig thriftBuckConfig; |
| private final FlavorDomain<ThriftLanguageSpecificEnhancer> enhancers; |
| |
| public ThriftLibraryDescription( |
| ThriftBuckConfig thriftBuckConfig, |
| ImmutableList<ThriftLanguageSpecificEnhancer> enhancers) { |
| |
| this.thriftBuckConfig = thriftBuckConfig; |
| |
| // Now build up a map indexing them by their flavor. |
| ImmutableMap.Builder<Flavor, ThriftLanguageSpecificEnhancer> enhancerMapBuilder = |
| ImmutableMap.builder(); |
| for (ThriftLanguageSpecificEnhancer enhancer : enhancers) { |
| enhancerMapBuilder.put(enhancer.getFlavor(), enhancer); |
| } |
| this.enhancers = new FlavorDomain<>("language", enhancerMapBuilder.build()); |
| } |
| |
| /** |
| * Return the path to use for the symlink tree we setup for the thrift files to be |
| * included by other rules. |
| */ |
| @VisibleForTesting |
| protected Path getIncludeRoot(BuildTarget target) { |
| return BuildTargets.getBinPath(target, "%s/include-symlink-tree"); |
| } |
| |
| @VisibleForTesting |
| protected BuildTarget createThriftIncludeSymlinkTreeTarget(BuildTarget target) { |
| return BuildTarget.builder(target).addFlavors(INCLUDE_SYMLINK_TREE_FLAVOR).build(); |
| } |
| |
| /** |
| * Create a unique build target to represent the compile rule for this thrift source for |
| * the given language. |
| */ |
| private static BuildTarget createThriftCompilerBuildTarget( |
| BuildTarget target, |
| String name) { |
| Preconditions.checkArgument(target.isFlavored()); |
| return BuildTarget |
| .builder(target) |
| .addFlavors( |
| ImmutableFlavor.of( |
| String.format( |
| "thrift-compile-%s", |
| name.replace('/', '-').replace('.', '-').replace('+', '-').replace(' ', '-')))) |
| .build(); |
| } |
| |
| /** |
| * Get the output directory for the generated sources used when compiling the given |
| * thrift source for the given language. |
| */ |
| @VisibleForTesting |
| protected Path getThriftCompilerOutputDir(BuildTarget target, String name) { |
| Preconditions.checkArgument(target.isFlavored()); |
| BuildTarget flavoredTarget = createThriftCompilerBuildTarget(target, name); |
| return BuildTargets.getGenPath(flavoredTarget, "%s/sources"); |
| } |
| |
| // Find all transitive thrift library dependencies of this rule. |
| private ImmutableSortedSet<ThriftLibrary> getTransitiveThriftLibraryDeps( |
| Iterable<ThriftLibrary> inputs) { |
| |
| final ImmutableSortedSet.Builder<ThriftLibrary> depsBuilder = ImmutableSortedSet.naturalOrder(); |
| |
| // Build up a graph of the inputs and their transitive dependencies. |
| new AbstractBreadthFirstTraversal<BuildRule>(inputs) { |
| @Override |
| public ImmutableSet<BuildRule> visit(BuildRule rule) { |
| ThriftLibrary thriftRule = (ThriftLibrary) rule; |
| depsBuilder.add(thriftRule); |
| return ImmutableSet.<BuildRule>copyOf(thriftRule.getThriftDeps()); |
| } |
| }.start(); |
| |
| return depsBuilder.build(); |
| } |
| |
| /** |
| * Build rule type to use for the rule which generates the language sources. |
| */ |
| private static final BuildRuleType THRIFT_COMPILE_TYPE = |
| ImmutableBuildRuleType.of("thrift_compile"); |
| |
| /** |
| * Create the build rules which compile the input thrift sources into their respective |
| * language specific sources. |
| */ |
| @VisibleForTesting |
| protected ImmutableMap<String, ThriftCompiler> createThriftCompilerBuildRules( |
| BuildRuleParams params, |
| BuildRuleResolver resolver, |
| ImmutableList<String> flags, |
| String language, |
| ImmutableSet<String> options, |
| ImmutableMap<String, SourcePath> srcs, |
| ImmutableSortedSet<ThriftLibrary> deps) { |
| |
| SourcePath compiler = thriftBuckConfig.getCompiler(); |
| |
| // Build up the include roots to find thrift file deps and also the build rules that |
| // generate them. |
| ImmutableMap.Builder<Path, SourcePath> includesBuilder = ImmutableMap.builder(); |
| ImmutableSortedSet.Builder<SymlinkTree> includeTreeRulesBuilder = |
| ImmutableSortedSet.naturalOrder(); |
| ImmutableList.Builder<Path> includeRootsBuilder = ImmutableList.builder(); |
| for (ThriftLibrary dep : deps) { |
| includesBuilder.putAll(dep.getIncludes()); |
| includeTreeRulesBuilder.add(dep.getIncludeTreeRule()); |
| includeRootsBuilder.add(dep.getIncludeTreeRule().getRoot()); |
| } |
| ImmutableMap<Path, SourcePath> includes = includesBuilder.build(); |
| ImmutableSortedSet<SymlinkTree> includeTreeRules = includeTreeRulesBuilder.build(); |
| ImmutableList<Path> includeRoots = includeRootsBuilder.build(); |
| |
| // For each thrift source, add a thrift compile rule to generate it's sources. |
| ImmutableMap.Builder<String, ThriftCompiler> compileRules = ImmutableMap.builder(); |
| for (ImmutableMap.Entry<String, SourcePath> ent : srcs.entrySet()) { |
| String name = ent.getKey(); |
| SourcePath source = ent.getValue(); |
| |
| BuildTarget target = createThriftCompilerBuildTarget(params.getBuildTarget(), name); |
| Path outputDir = getThriftCompilerOutputDir(params.getBuildTarget(), name); |
| |
| compileRules.put( |
| name, |
| new ThriftCompiler( |
| params.copyWithChanges( |
| THRIFT_COMPILE_TYPE, |
| target, |
| Suppliers.ofInstance( |
| ImmutableSortedSet.<BuildRule>naturalOrder() |
| .addAll( |
| new SourcePathResolver(resolver).filterBuildRuleInputs( |
| ImmutableList.<SourcePath>builder() |
| .add(compiler) |
| .add(source) |
| .addAll(includes.values()) |
| .build())) |
| .addAll(includeTreeRules) |
| .build()), |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), |
| new SourcePathResolver(resolver), |
| compiler, |
| flags, |
| outputDir, |
| source, |
| language, |
| options, |
| includeRoots, |
| includes)); |
| } |
| |
| return compileRules.build(); |
| } |
| |
| /** |
| * Downcast the given deps to {@link ThriftLibrary} rules, throwing an error if we see an |
| * unexpected type. |
| */ |
| private static ImmutableSortedSet<ThriftLibrary> resolveThriftDeps( |
| BuildTarget target, |
| Iterable<BuildRule> deps) { |
| |
| ImmutableSortedSet.Builder<ThriftLibrary> libDepsBuilder = ImmutableSortedSet.naturalOrder(); |
| for (BuildRule dep : deps) { |
| if (!(dep instanceof ThriftLibrary)) { |
| throw new HumanReadableException( |
| "%s: parameter \"deps\": \"%s\" (%s) is not a thrift_library", |
| target, |
| dep.getBuildTarget(), |
| dep.getType()); |
| } |
| libDepsBuilder.add((ThriftLibrary) dep); |
| } |
| |
| return libDepsBuilder.build(); |
| } |
| |
| @Override |
| public <A extends ThriftConstructorArg> BuildRule createBuildRule( |
| BuildRuleParams params, |
| BuildRuleResolver resolver, |
| A args) { |
| |
| BuildTarget target = params.getBuildTarget(); |
| |
| // Extract the thrift language we're using from our build target. |
| Optional<Map.Entry<Flavor, ThriftLanguageSpecificEnhancer>> enhancerFlavor; |
| try { |
| enhancerFlavor = enhancers.getFlavorAndValue(ImmutableSet.copyOf(target.getFlavors())); |
| } catch (FlavorDomainException e) { |
| throw new HumanReadableException("%s: %s", target, e.getMessage()); |
| } |
| |
| SourcePathResolver pathResolver = new SourcePathResolver(resolver); |
| ImmutableMap<String, SourcePath> namedSources = |
| pathResolver.getSourcePathNames(target, "srcs", args.srcs.keySet()); |
| |
| // The dependencies listed in "deps", which should all be of type "ThriftLibrary". |
| ImmutableSortedSet<ThriftLibrary> thriftDeps = |
| resolveThriftDeps( |
| target, |
| resolver.getAllRules(args.deps.or(ImmutableSortedSet.<BuildTarget>of()))); |
| |
| // The unflavored version of this rule is responsible for setting up the the various |
| // build rules to facilitate dependents including it's thrift sources. |
| if (!enhancerFlavor.isPresent()) { |
| |
| // Namespace the thrift files using our target's base path. |
| ImmutableMap.Builder<Path, SourcePath> includesBuilder = ImmutableMap.builder(); |
| for (ImmutableMap.Entry<String, SourcePath> entry : namedSources.entrySet()) { |
| includesBuilder.put( |
| target.getBasePath().resolve(entry.getKey()), |
| entry.getValue()); |
| } |
| ImmutableMap<Path, SourcePath> includes = includesBuilder.build(); |
| |
| // Create the symlink tree build rule and add it to the resolver. |
| Path includeRoot = getIncludeRoot(target); |
| BuildTarget symlinkTreeTarget = createThriftIncludeSymlinkTreeTarget(target); |
| SymlinkTree symlinkTree = new SymlinkTree( |
| params.copyWithChanges( |
| INCLUDE_SYMLINK_TREE_TYPE, |
| symlinkTreeTarget, |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()), |
| Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), |
| pathResolver, |
| includeRoot, |
| includes); |
| resolver.addToIndex(symlinkTree); |
| |
| // Create a dummy rule that dependents can use to grab the information they need |
| // about this rule from the action graph. |
| return new ThriftLibrary( |
| params, |
| pathResolver, |
| thriftDeps, |
| symlinkTree, |
| includes); |
| } |
| |
| ThriftLanguageSpecificEnhancer enhancer = enhancerFlavor.get().getValue(); |
| String language = enhancer.getLanguage(); |
| ImmutableSet<String> options = enhancer.getOptions(target, args); |
| ImmutableSet<BuildTarget> implicitDeps = enhancer.getImplicitDepsForTargetFromConstructorArg( |
| target, |
| args); |
| |
| // Lookup the thrift library corresponding to this rule. We add an implicit dep onto |
| // this rule in the findImplicitDepsFromParams method, so this should always exist by |
| // the time we get here. |
| ThriftLibrary thriftLibrary = |
| (ThriftLibrary) resolver.getRule( |
| BuildTarget.of(target.getUnflavoredBuildTarget())); |
| |
| // We implicitly pass the language-specific flavors of your thrift lib dependencies as |
| // language specific deps to the language specific enhancer. |
| ImmutableSortedSet<BuildRule> languageSpecificDeps = BuildRules.toBuildRulesFor( |
| target, |
| resolver, |
| Iterables.concat( |
| BuildTargets.propagateFlavorDomains( |
| target, |
| ImmutableList.<FlavorDomain<?>>of(enhancers), |
| FluentIterable.from(thriftDeps) |
| .transform(HasBuildTarget.TO_TARGET)), |
| implicitDeps), |
| false); |
| |
| // Create a a build rule for thrift source file, to compile the language specific sources. |
| // They keys in this map are the logical names of the thrift files (e.g as specific in a BUCK |
| // file, such as "test.thrift"). |
| ImmutableMap<String, ThriftCompiler> compilerRules = createThriftCompilerBuildRules( |
| params, |
| resolver, |
| args.flags.or(ImmutableList.<String>of()), |
| language, |
| options, |
| namedSources, |
| ImmutableSortedSet.<ThriftLibrary>naturalOrder() |
| .add(thriftLibrary) |
| .addAll(getTransitiveThriftLibraryDeps(thriftDeps)) |
| .build()); |
| resolver.addAllToIndex(compilerRules.values()); |
| |
| // Build up the map of {@link ThriftSource} objects to pass the language specific enhancer. |
| // They keys in this map are the logical names of the thrift files (e.g as specific in a BUCK |
| // file, such as "test.thrift"). |
| ImmutableMap.Builder<String, ThriftSource> thriftSourceBuilder = ImmutableMap.builder(); |
| for (ImmutableMap.Entry<String, SourcePath> ent : namedSources.entrySet()) { |
| thriftSourceBuilder.put( |
| ent.getKey(), |
| new ThriftSource( |
| Preconditions.checkNotNull(compilerRules.get(ent.getKey())), |
| Preconditions.checkNotNull(args.srcs.get(ent.getValue())), |
| getThriftCompilerOutputDir(target, ent.getKey()))); |
| } |
| ImmutableMap<String, ThriftSource> thriftSources = thriftSourceBuilder.build(); |
| |
| // Generate language specific rules. |
| return enhancer.createBuildRule( |
| params, |
| resolver, |
| args, |
| thriftSources, |
| languageSpecificDeps); |
| } |
| |
| @Override |
| public ThriftConstructorArg createUnpopulatedConstructorArg() { |
| return new ThriftConstructorArg(); |
| } |
| |
| @Override |
| public BuildRuleType getBuildRuleType() { |
| return TYPE; |
| } |
| |
| @Override |
| public boolean hasFlavors(ImmutableSet<Flavor> flavors) { |
| return enhancers.containsAnyOf(flavors) || flavors.isEmpty(); |
| } |
| |
| /** |
| * Collect implicit deps for the thrift compiler and language specific enhancers. |
| */ |
| @Override |
| public Iterable<BuildTarget> findDepsForTargetFromConstructorArgs( |
| BuildTarget buildTarget, |
| ThriftConstructorArg arg) { |
| Optional<Map.Entry<Flavor, ThriftLanguageSpecificEnhancer>> enhancerFlavor; |
| try { |
| enhancerFlavor = enhancers.getFlavorAndValue(ImmutableSet.copyOf(buildTarget.getFlavors())); |
| } catch (FlavorDomainException e) { |
| throw new HumanReadableException("%s: %s", buildTarget, e.getMessage()); |
| } |
| |
| // The unflavored target represents the actual thrift library, which doesn't need |
| // any implicit deps. |
| if (!enhancerFlavor.isPresent()) { |
| return ImmutableList.of(); |
| } |
| |
| List<BuildTarget> deps = Lists.newArrayList(); |
| |
| // The flavored versions of this rule must always implicitly depend on the non-flavored |
| // version, as it sets up the include rules for dependents. |
| deps.add(BuildTarget.of(buildTarget.getUnflavoredBuildTarget())); |
| |
| // Convert all the thrift library deps into their flavored counterparts and |
| // add them to our list of deps, to make sure they get included in the target graph. |
| deps.addAll( |
| BuildTargets.propagateFlavorDomains( |
| buildTarget, |
| ImmutableList.<FlavorDomain<?>>of(enhancers), |
| arg.deps.get())); |
| |
| // Add the compiler target, if there is one. |
| SourcePath compiler = thriftBuckConfig.getCompiler(); |
| if (compiler instanceof BuildTargetSourcePath) { |
| deps.add(((BuildTargetSourcePath) compiler).getTarget()); |
| } |
| |
| // Grab the language specific implicit dependencies and add their raw target representations |
| // to our list. |
| ThriftLanguageSpecificEnhancer enhancer = enhancerFlavor.get().getValue(); |
| ImmutableSet<BuildTarget> implicitDeps = enhancer.getImplicitDepsForTargetFromConstructorArg( |
| buildTarget, |
| arg); |
| deps.addAll(implicitDeps); |
| |
| return deps; |
| } |
| |
| } |