/* * 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.CxxPrepareForLinkStep; import com.facebook.buck.cxx.Linker; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.rules.AbstractBuildRule; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.Tool; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.keys.SupportsInputBasedRuleKey; import com.facebook.buck.shell.ShellStep; import com.facebook.buck.shell.SymlinkFilesIntoDirectoryStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.Verbosity; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import java.nio.file.Path; import java.util.stream.Stream; /** Generate a rustc command line with all appropriate dependencies in place. */ public class RustCompileRule extends AbstractBuildRule implements SupportsInputBasedRuleKey { @AddToRuleKey private final Tool compiler; @AddToRuleKey private final Linker linker; @AddToRuleKey private final ImmutableList<Arg> args; @AddToRuleKey private final ImmutableList<Arg> linkerArgs; @AddToRuleKey private final SourcePath rootModule; @AddToRuleKey private final ImmutableSortedSet<SourcePath> srcs; private final Path scratchDir; private final String filename; @AddToRuleKey private final boolean hasOutput; /** * Work out how to invoke the Rust compiler, rustc. * * <p>In Rust, a crate is the equivalent of a package in other languages. It's also the basic unit * of compilation. * * <p>A crate can either be a "binary crate" - which generates an executable - or a "library * crate", which makes an .rlib file. .rlib files contain both interface details (function * signatures, inline functions, macros, etc) and compiled object code, and so are equivalent to * both header files and library archives. There are also dynamic crates which compile to .so * files. * * <p>All crates are compiled from at least one source file, which is its main (or top, or root) * module. It may have references to other modules, which may be in other source files. Rustc only * needs the main module filename and will find the rest of the source files from there (akin to * #include in C/C++). If the crate also has dependencies on other crates, then those .rlib files * must also be passed to rustc for the interface details, and to be linked if its a binary crate. */ protected RustCompileRule( BuildRuleParams buildRuleParams, String filename, Tool compiler, Linker linker, ImmutableList<Arg> args, ImmutableList<Arg> linkerArgs, ImmutableSortedSet<SourcePath> srcs, SourcePath rootModule, boolean hasOutput) { super(buildRuleParams); this.filename = filename; this.compiler = compiler; this.linker = linker; this.args = args; this.linkerArgs = linkerArgs; this.rootModule = rootModule; this.srcs = srcs; this.scratchDir = BuildTargets.getScratchPath(getProjectFilesystem(), getBuildTarget(), "%s-container"); this.hasOutput = hasOutput; } public static RustCompileRule from( SourcePathRuleFinder ruleFinder, BuildRuleParams params, String filename, Tool compiler, Linker linker, ImmutableList<Arg> args, ImmutableList<Arg> linkerArgs, ImmutableSortedSet<SourcePath> sources, SourcePath rootModule, boolean hasOutput) { return new RustCompileRule( params.copyReplacingExtraDeps( Suppliers.memoize( () -> ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(compiler.getDeps(ruleFinder)) .addAll(linker.getDeps(ruleFinder)) .addAll( Stream.of(args, linkerArgs) .flatMap( a -> a.stream().flatMap(arg -> arg.getDeps(ruleFinder).stream())) .iterator()) .addAll(ruleFinder.filterBuildRuleInputs(ImmutableList.of(rootModule))) .addAll(ruleFinder.filterBuildRuleInputs(sources)) .build())), filename, compiler, linker, args, linkerArgs, sources, rootModule, hasOutput); } protected static Path getOutputDir(BuildTarget target, ProjectFilesystem filesystem) { return BuildTargets.getGenPath(filesystem, target, "%s"); } private Path getOutput() { return getOutputDir(getBuildTarget(), getProjectFilesystem()).resolve(filename); } @Override public ImmutableList<Step> getBuildSteps( BuildContext buildContext, BuildableContext buildableContext) { Path output = getOutput(); if (hasOutput) { buildableContext.recordArtifact(output); } SourcePathResolver resolver = buildContext.getSourcePathResolver(); Path argFilePath = getProjectFilesystem() .getRootPath() .resolve( BuildTargets.getScratchPath( getProjectFilesystem(), getBuildTarget(), "%s.argsfile")); Path fileListPath = getProjectFilesystem() .getRootPath() .resolve( BuildTargets.getScratchPath( getProjectFilesystem(), getBuildTarget(), "%s__filelist.txt")); return new ImmutableList.Builder<Step>() .addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), scratchDir)) .add( new SymlinkFilesIntoDirectoryStep( getProjectFilesystem(), getProjectFilesystem().getRootPath(), srcs.stream() .map(resolver::getRelativePath) .collect(MoreCollectors.toImmutableList()), scratchDir)) .addAll( MakeCleanDirectoryStep.of( getProjectFilesystem(), getOutputDir(getBuildTarget(), getProjectFilesystem()))) .addAll( CxxPrepareForLinkStep.create( argFilePath, fileListPath, linker.fileList(fileListPath), output, linkerArgs, linker, getBuildTarget().getCellPath(), resolver)) .add( new ShellStep(getProjectFilesystem().getRootPath()) { @Override protected ImmutableList<String> getShellCommandInternal( ExecutionContext executionContext) { ImmutableList<String> linkerCmd = linker.getCommandPrefix(resolver); ImmutableList.Builder<String> cmd = ImmutableList.builder(); Path src = scratchDir.resolve(resolver.getRelativePath(rootModule)); cmd.addAll(compiler.getCommandPrefix(resolver)) .addAll( executionContext.getAnsi().isAnsiTerminal() ? ImmutableList.of("--color=always") : ImmutableList.of()) .add(String.format("-Clinker=%s", linkerCmd.get(0))) .add(String.format("-Clink-arg=@%s", argFilePath)) .addAll(Arg.stringify(args, buildContext.getSourcePathResolver())) .add("-o", output.toString()) .add(src.toString()); return cmd.build(); } /* * Make sure all stderr output from rustc is emitted, since its either a warning or an * error. In general Rust code should have zero warnings, or all warnings as errors. * Regardless, respect requests for silence. */ @Override protected boolean shouldPrintStderr(Verbosity verbosity) { return !verbosity.isSilent(); } @Override public ImmutableMap<String, String> getEnvironmentVariables( ExecutionContext context) { return compiler.getEnvironment(buildContext.getSourcePathResolver()); } @Override public String getShortName() { return "rust-build"; } }) .build(); } @Override public SourcePath getSourcePathToOutput() { return new ExplicitBuildTargetSourcePath(getBuildTarget(), getOutput()); } SourcePath getCrateRoot() { return rootModule; } }