Java tutorial
/* * 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.HeaderSymlinkTree; import com.facebook.buck.graph.AbstractBreadthFirstTraversal; import com.facebook.buck.io.ProjectFilesystem; 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.Flavored; import com.facebook.buck.model.HasBuildTarget; import com.facebook.buck.model.ImmutableFlavor; 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.BuildRules; import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.ImplicitDepsInferringDescription; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.Tool; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.OptionalCompat; import com.google.common.annotations.VisibleForTesting; 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; import java.util.Optional; public class ThriftLibraryDescription implements Description<ThriftConstructorArg>, Flavored, ImplicitDepsInferringDescription<ThriftConstructorArg> { private static final Flavor INCLUDE_SYMLINK_TREE_FLAVOR = ImmutableFlavor.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. this.enhancers = FlavorDomain.from("language", enhancers); } /** * 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, ProjectFilesystem filesystem) { return BuildTargets.getScratchPath(filesystem, 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(ProjectFilesystem filesystem, BuildTarget target, String name) { Preconditions.checkArgument(target.isFlavored()); BuildTarget flavoredTarget = createThriftCompilerBuildTarget(target, name); return BuildTargets.getGenPath(filesystem, 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.copyOf(thriftRule.getThriftDeps()); } }.start(); return depsBuilder.build(); } /** * 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, CompilerType compilerType, ImmutableList<String> flags, String language, ImmutableSet<String> options, ImmutableMap<String, SourcePath> srcs, ImmutableSortedSet<ThriftLibrary> deps, ImmutableMap<String, ImmutableSortedSet<String>> generatedSources) { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); Tool compiler = thriftBuckConfig.getCompiler(compilerType, resolver); // 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<HeaderSymlinkTree> includeTreeRulesBuilder = ImmutableSortedSet.naturalOrder(); ImmutableList.Builder<Path> includeRootsBuilder = ImmutableList.builder(); ImmutableSet.Builder<Path> headerMapsBuilder = ImmutableSet.builder(); for (ThriftLibrary dep : deps) { includesBuilder.putAll(dep.getIncludes()); includeTreeRulesBuilder.add(dep.getIncludeTreeRule()); includeRootsBuilder.add(dep.getIncludeTreeRule().getIncludePath()); headerMapsBuilder.addAll(OptionalCompat.asSet(dep.getIncludeTreeRule().getHeaderMap())); } ImmutableMap<Path, SourcePath> includes = includesBuilder.build(); ImmutableSortedSet<HeaderSymlinkTree> includeTreeRules = includeTreeRulesBuilder.build(); ImmutableList<Path> includeRoots = includeRootsBuilder.build(); ImmutableSet<Path> headerMaps = headerMapsBuilder.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(); ImmutableSortedSet<String> genSrcs = Preconditions.checkNotNull(generatedSources.get(name)); BuildTarget target = createThriftCompilerBuildTarget(params.getBuildTarget(), name); Path outputDir = getThriftCompilerOutputDir(params.getProjectFilesystem(), params.getBuildTarget(), name); compileRules.put(name, new ThriftCompiler( params.copyWithChanges(target, Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(compiler.getDeps(ruleFinder)) .addAll(ruleFinder.filterBuildRuleInputs(ImmutableList.<SourcePath>builder().add(source) .addAll(includes.values()).build())) .addAll(includeTreeRules).build()), Suppliers.ofInstance(ImmutableSortedSet.of())), compiler, flags, outputDir, source, language, options, includeRoots, headerMaps, includes, genSrcs)); } 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(TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, A args) throws NoSuchBuildTargetException { BuildTarget target = params.getBuildTarget(); // Extract the thrift language we're using from our build target. Optional<Map.Entry<Flavor, ThriftLanguageSpecificEnhancer>> enhancerFlavor = enhancers .getFlavorAndValue(target); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(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)); // 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, params.getProjectFilesystem()); BuildTarget symlinkTreeTarget = createThriftIncludeSymlinkTreeTarget(target); HeaderSymlinkTree symlinkTree = new HeaderSymlinkTree(params.copyWithChanges(symlinkTreeTarget, Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.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.of(enhancers), FluentIterable.from(thriftDeps).transform(HasBuildTarget::getBuildTarget)), implicitDeps)); // Form the set of generated sources, so that compiler rules know what output paths to record. ImmutableMap.Builder<String, ImmutableSortedSet<String>> generatedSourcesBuilder = ImmutableMap.builder(); for (ImmutableMap.Entry<String, SourcePath> ent : namedSources.entrySet()) { String thriftName = ent.getKey(); ImmutableList<String> services = Preconditions.checkNotNull(args.srcs.get(ent.getValue())); generatedSourcesBuilder.put(thriftName, enhancer.getGeneratedSources(target, args, thriftName, services)); } ImmutableMap<String, ImmutableSortedSet<String>> generatedSources = generatedSourcesBuilder.build(); // 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, enhancer.getCompilerType(), args.flags, language, options, namedSources, ImmutableSortedSet.<ThriftLibrary>naturalOrder().add(thriftLibrary) .addAll(getTransitiveThriftLibraryDeps(thriftDeps)).build(), generatedSources); 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()) { ImmutableList<String> services = Preconditions.checkNotNull(args.srcs.get(ent.getValue())); ThriftCompiler compilerRule = Preconditions.checkNotNull(compilerRules.get(ent.getKey())); thriftSourceBuilder.put(ent.getKey(), new ThriftSource(compilerRule, services, getThriftCompilerOutputDir(params.getProjectFilesystem(), target, ent.getKey()))); } ImmutableMap<String, ThriftSource> thriftSources = thriftSourceBuilder.build(); // Generate language specific rules. return enhancer.createBuildRule(targetGraph, params, resolver, args, thriftSources, languageSpecificDeps); } @Override public ThriftConstructorArg createUnpopulatedConstructorArg() { return new ThriftConstructorArg(); } @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, CellPathResolver cellRoots, ThriftConstructorArg arg) { Optional<Map.Entry<Flavor, ThriftLanguageSpecificEnhancer>> enhancerFlavor = enhancers .getFlavorAndValue(buildTarget); // 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.of(enhancers), arg.deps)); // Add the compiler target, if there is one. deps.addAll(OptionalCompat .asSet(thriftBuckConfig.getCompilerTarget(enhancerFlavor.get().getValue().getCompilerType()))); // 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; } // The version of thrift compiler to use. public enum CompilerType { THRIFT, THRIFT2 } }