Java tutorial
/* * Copyright 2017-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.rust; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.Linker; import com.facebook.buck.cxx.Linkers; import com.facebook.buck.cxx.NativeLinkables; import com.facebook.buck.graph.AbstractBreadthFirstTraversal; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.FlavorDomain; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BinaryWrapperRule; 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.CommandTool; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.SymlinkTree; import com.facebook.buck.rules.Tool; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.args.HasSourcePath; import com.facebook.buck.rules.args.SourcePathArg; import com.facebook.buck.rules.args.StringArg; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import java.nio.file.Path; import java.util.Iterator; import java.util.Optional; import java.util.stream.Stream; /** * Utilities to generate various kinds of Rust compilation. */ public class RustCompileUtils { private RustCompileUtils() { } protected static BuildTarget getCompileBuildTarget(BuildTarget target, CxxPlatform cxxPlatform, CrateType crateType) { return target.withFlavors(cxxPlatform.getFlavor(), crateType.getFlavor()); } // Construct a RustCompileRule with: // - all sources // - rustc // - linker // - rustc optim / feature / cfg / user-specified flags // - linker args // - `--extern <crate>=<rlibpath>` for direct dependencies // - `-L dependency=<dir>` for transitive dependencies (?) // - `-L native=<dir>` // - `-C relocation-model=pic/static/default/dynamic-no-pic` according to flavor // - `--crate-type lib/rlib/dylib/cdylib/staticlib` according to flavor protected static RustCompileRule createBuild(BuildTarget target, String crateName, BuildRuleParams params, BuildRuleResolver resolver, SourcePathResolver pathResolver, SourcePathRuleFinder ruleFinder, CxxPlatform cxxPlatform, RustBuckConfig rustConfig, ImmutableList<String> extraFlags, ImmutableList<String> extraLinkerFlags, Iterable<Arg> linkerInputs, CrateType crateType, Linker.LinkableDepType depType, boolean rpath, ImmutableSortedSet<SourcePath> sources, SourcePath rootModule) throws NoSuchBuildTargetException { ImmutableSortedSet<BuildRule> ruledeps = params.getDeps(); ImmutableList.Builder<Arg> linkerArgs = ImmutableList.builder(); Stream.concat(rustConfig.getLinkerArgs(cxxPlatform).stream(), extraLinkerFlags.stream()).map(StringArg::new) .forEach(linkerArgs::add); linkerArgs.addAll(linkerInputs); ImmutableList.Builder<Arg> args = ImmutableList.builder(); String relocModel; if (crateType.isPic()) { relocModel = "pic"; } else { relocModel = "static"; } Stream.of(Stream.of(String.format("--crate-name=%s", crateName), String.format("--crate-type=%s", crateType), String.format("-Crelocation-model=%s", relocModel)), extraFlags.stream()).flatMap(x -> x).map(StringArg::new).forEach(args::add); // Find direct and transitive Rust deps. This could end up with a lot of redundant // parameters (lots of rlibs in one directory), but Arg isn't comparable, so we can't // put it in a Set. new AbstractBreadthFirstTraversal<BuildRule>(ruledeps) { private final ImmutableSet<BuildRule> empty = ImmutableSet.of(); @Override public Iterable<BuildRule> visit(BuildRule rule) { ImmutableSet<BuildRule> deps = empty; if (rule instanceof RustLinkable) { deps = rule.getDeps(); Arg arg = ((RustLinkable) rule).getLinkerArg(ruledeps.contains(rule), cxxPlatform, depType); args.add(arg); } return deps; } }.start(); // A native crate output is no longer intended for consumption by the Rust toolchain; // it's either an executable, or a native library that C/C++ can link with. Rust DYLIBs // also need all dependencies available. if (crateType.needAllDeps()) { ImmutableList<Arg> nativeArgs = NativeLinkables.getTransitiveNativeLinkableInput(cxxPlatform, ruledeps, depType, RustLinkable.class::isInstance, RustLinkable.class::isInstance).getArgs(); // Add necessary rpaths if we're dynamically linking with things // XXX currently breaks on paths containing commas, which all of ours do: see // https://github.com/rust-lang/rust/issues/38795 if (rpath && depType == Linker.LinkableDepType.SHARED) { args.add(new StringArg("-Crpath")); } linkerArgs.addAll(nativeArgs); // Also let rustc know about libraries as native deps (in case someone is using #[link] // even though it won't generally work with Buck) nativeArgs.stream().filter(HasSourcePath.class::isInstance).map(sp -> (HasSourcePath) sp) .map(sp -> sp.getPathResolver().getRelativePath(sp.getPath()).getParent()) .map(dir -> String.format("-Lnative=%s", dir)).map(StringArg::new).forEach(args::add); } // If we want shared deps or are building a dynamic rlib, make sure we prefer // dynamic dependencies (esp to get dynamic dependency on standard libs) if (depType == Linker.LinkableDepType.SHARED || crateType == CrateType.DYLIB) { args.add(new StringArg("-Cprefer-dynamic")); } String filename = crateType.filenameFor(crateName, cxxPlatform); return resolver.addToIndex(RustCompileRule.from(ruleFinder, params.copyWithBuildTarget(target), pathResolver, filename, rustConfig.getRustCompiler().resolve(resolver), rustConfig.getLinkerProvider(cxxPlatform, cxxPlatform.getLd().getType()).resolve(resolver), args.build(), linkerArgs.build(), sources, rootModule)); } public static RustCompileRule requireBuild(String crateName, BuildRuleParams params, BuildRuleResolver resolver, SourcePathResolver pathResolver, SourcePathRuleFinder ruleFinder, CxxPlatform cxxPlatform, RustBuckConfig rustConfig, ImmutableList<String> extraFlags, ImmutableList<String> extraLinkerFlags, Iterable<Arg> linkerInputs, CrateType crateType, Linker.LinkableDepType depType, ImmutableSortedSet<SourcePath> sources, SourcePath rootModule) throws NoSuchBuildTargetException { BuildTarget target = getCompileBuildTarget(params.getBuildTarget(), cxxPlatform, crateType); // If this rule has already been generated, return it. Optional<RustCompileRule> existing = resolver.getRuleOptionalWithType(target, RustCompileRule.class); if (existing.isPresent()) { return existing.get(); } return createBuild(target, crateName, params, resolver, pathResolver, ruleFinder, cxxPlatform, rustConfig, extraFlags, extraLinkerFlags, linkerInputs, crateType, depType, true, sources, rootModule); } public static Linker.LinkableDepType getLinkStyle(BuildTarget target, Optional<Linker.LinkableDepType> linkStyle) { Optional<RustBinaryDescription.Type> type = RustBinaryDescription.BINARY_TYPE.getValue(target); Linker.LinkableDepType ret; if (type.isPresent()) { ret = type.get().getLinkStyle(); } else if (linkStyle.isPresent()) { ret = linkStyle.get(); } else { ret = Linker.LinkableDepType.STATIC; } // XXX rustc always links executables with "-pie", which requires all objects to be built // with a PIC relocation model (-fPIC or -Crelocation-model=pic). Rust code does this by // default, but we need to make sure any C/C++ dependencies are also PIC. // So for now, remap STATIC -> STATIC_PIC, until we can control rustc's use of -pie. if (ret == Linker.LinkableDepType.STATIC) { ret = Linker.LinkableDepType.STATIC_PIC; } return ret; } public static BinaryWrapperRule createBinaryBuildRule(BuildRuleParams params, BuildRuleResolver resolver, RustBuckConfig rustBuckConfig, FlavorDomain<CxxPlatform> cxxPlatforms, CxxPlatform defaultCxxPlatform, Optional<String> crateName, ImmutableSortedSet<String> features, Iterator<String> rustcFlags, Iterator<String> linkerFlags, Linker.LinkableDepType linkStyle, boolean rpath, ImmutableSortedSet<SourcePath> srcs, Optional<SourcePath> crateRoot, ImmutableSet<String> defaultRoots) throws NoSuchBuildTargetException { final BuildTarget buildTarget = params.getBuildTarget(); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); ImmutableList.Builder<String> rustcArgs = ImmutableList.builder(); RustCompileUtils.addFeatures(buildTarget, features, rustcArgs); rustcArgs.addAll(rustcFlags); ImmutableList.Builder<String> linkerArgs = ImmutableList.builder(); linkerArgs.addAll(linkerFlags); String crate = crateName.orElse(ruleToCrateName(buildTarget.getShortName())); Optional<SourcePath> rootModule = RustCompileUtils.getCrateRoot(pathResolver, crate, crateRoot, defaultRoots, srcs.stream()); if (!rootModule.isPresent()) { throw new HumanReadableException("Can't find suitable top-level source file for %s", buildTarget.getShortName()); } CxxPlatform cxxPlatform = cxxPlatforms.getValue(params.getBuildTarget()).orElse(defaultCxxPlatform); // The target to use for the link rule. BuildTarget binaryTarget = params.getBuildTarget().withAppendedFlavors(cxxPlatform.getFlavor(), RustDescriptionEnhancer.RFBIN); CommandTool.Builder executableBuilder = new CommandTool.Builder(); // Add the binary as the first argument. executableBuilder.addArg(new SourcePathArg(pathResolver, new BuildTargetSourcePath(binaryTarget))); // Special handling for dynamically linked binaries. if (linkStyle == Linker.LinkableDepType.SHARED) { // Create a symlink tree with for all shared libraries needed by this binary. SymlinkTree sharedLibraries = resolver.addToIndex( CxxDescriptionEnhancer.createSharedLibrarySymlinkTree(params, pathResolver, cxxPlatform, params.getDeps(), RustLinkable.class::isInstance, RustLinkable.class::isInstance)); // Embed a origin-relative library path into the binary so it can find the shared libraries. // The shared libraries root is absolute. Also need an absolute path to the linkOutput Path absBinaryDir = params.getBuildTarget().getCellPath() .resolve(RustCompileRule.getOutputDir(binaryTarget, params.getProjectFilesystem())); linkerArgs.addAll(Linkers.iXlinker("-rpath", String.format("%s/%s", cxxPlatform.getLd().resolve(resolver).origin(), absBinaryDir.relativize(sharedLibraries.getRoot()).toString()))); // Add all the shared libraries and the symlink tree as inputs to the tool that represents // this binary, so that users can attach the proper deps. executableBuilder.addDep(sharedLibraries); executableBuilder.addInputs(sharedLibraries.getLinks().values()); } final CommandTool executable = executableBuilder.build(); final RustCompileRule buildRule = RustCompileUtils.createBuild(binaryTarget, crate, params, resolver, pathResolver, ruleFinder, cxxPlatform, rustBuckConfig, rustcArgs.build(), linkerArgs.build(), /* linkerInputs */ ImmutableList.of(), CrateType.BIN, linkStyle, rpath, srcs, rootModule.get()); return new BinaryWrapperRule(params.appendExtraDeps(buildRule), pathResolver, ruleFinder) { @Override public Tool getExecutableCommand() { return executable; } @Override public Path getPathToOutput() { return buildRule.getPathToOutput(); } }; } /** * Given a list of sources, return the one which is the root based on the defaults and user * parameters. * * @param resolver Source path resolver for rule * @param crate Name of crate * @param defaults Default names for this rule (library, binary, etc) * @param sources List of sources * @return The matching source */ public static Optional<SourcePath> getCrateRoot(SourcePathResolver resolver, String crate, Optional<SourcePath> crateRoot, ImmutableSet<String> defaults, Stream<SourcePath> sources) { String crateName = String.format("%s.rs", crate); ImmutableList<SourcePath> res = sources.filter(src -> { String name = resolver.getRelativePath(src).getFileName().toString(); return crateRoot.map(src::equals).orElse(false) || defaults.contains(name) || name.equals(crateName); }).collect(MoreCollectors.toImmutableList()); if (res.size() == 1) { return Optional.of(res.get(0)); } else { return Optional.empty(); } } public static void addFeatures(BuildTarget buildTarget, Iterable<String> features, ImmutableList.Builder<String> args) { for (String feature : features) { if (feature.contains("\"")) { throw new HumanReadableException("%s contains an invalid feature name %s", buildTarget.getFullyQualifiedName(), feature); } args.add("--cfg", String.format("feature=\"%s\"", feature)); } } public static String ruleToCrateName(String rulename) { return rulename.replace('-', '_'); } }