blob: d0651bfc62ebd160884a68e23971a85b3aae4974 [file]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gn/ninja_rust_binary_target_writer.h"
#include <sstream>
#include <unordered_set>
#include "base/strings/string_util.h"
#include "gn/deps_iterator.h"
#include "gn/filesystem_utils.h"
#include "gn/general_tool.h"
#include "gn/ninja_target_command_util.h"
#include "gn/ninja_utils.h"
#include "gn/rust_substitution_type.h"
#include "gn/rust_values.h"
#include "gn/substitution_writer.h"
#include "gn/target.h"
namespace {
// Returns the proper escape options for writing compiler and linker flags.
EscapeOptions GetFlagOptions() {
EscapeOptions opts;
opts.mode = ESCAPE_NINJA_COMMAND;
return opts;
}
void WriteVar(std::string_view name,
const std::string& value,
EscapeOptions opts,
std::vector<NinjaVariable>& target_vars) {
std::ostringstream ss;
EscapeStringToStream(ss, value, opts);
target_vars.emplace_back(name, ss.str());
}
void WriteCrateVars(const Target* target,
const Tool* tool,
EscapeOptions opts,
std::vector<NinjaVariable>& target_vars) {
WriteVar(kRustSubstitutionCrateName.ninja_name,
target->rust_values().crate_name(), opts, target_vars);
std::string crate_type;
switch (target->rust_values().crate_type()) {
// Auto-select the crate type for executables, static libraries, and rlibs.
case RustValues::CRATE_AUTO: {
switch (target->output_type()) {
case Target::EXECUTABLE:
crate_type = "bin";
break;
case Target::STATIC_LIBRARY:
crate_type = "staticlib";
break;
case Target::RUST_LIBRARY:
crate_type = "rlib";
break;
case Target::RUST_PROC_MACRO:
crate_type = "proc-macro";
break;
default:
NOTREACHED();
}
break;
}
case RustValues::CRATE_BIN:
crate_type = "bin";
break;
case RustValues::CRATE_CDYLIB:
crate_type = "cdylib";
break;
case RustValues::CRATE_DYLIB:
crate_type = "dylib";
break;
case RustValues::CRATE_PROC_MACRO:
crate_type = "proc-macro";
break;
case RustValues::CRATE_RLIB:
crate_type = "rlib";
break;
case RustValues::CRATE_STATICLIB:
crate_type = "staticlib";
break;
default:
NOTREACHED();
}
WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts,
target_vars);
WriteVar(SubstitutionOutputExtension.ninja_name,
SubstitutionWriter::GetLinkerSubstitution(
target, tool, &SubstitutionOutputExtension),
opts, target_vars);
WriteVar(SubstitutionOutputDir.ninja_name,
SubstitutionWriter::GetLinkerSubstitution(target, tool,
&SubstitutionOutputDir),
opts, target_vars);
}
} // namespace
NinjaRustBinaryTargetWriter::NinjaRustBinaryTargetWriter(const Target* target,
std::ostream& out)
: NinjaBinaryTargetWriter(target, out),
tool_(target->toolchain()->GetToolForTargetFinalOutputAsRust(target)) {}
NinjaRustBinaryTargetWriter::~NinjaRustBinaryTargetWriter() = default;
// TODO(juliehockett): add inherited library support? and IsLinkable support?
// for c-cross-compat
void NinjaRustBinaryTargetWriter::GenerateRules() {
DCHECK(target_->output_type() != Target::SOURCE_SET);
size_t num_output_uses = target_->sources().size();
std::vector<OutputFile> input_deps =
WriteInputsStampOrPhonyAndGetDep(num_output_uses);
WriteCompilerVars();
// Classify our dependencies.
ClassifiedDeps classified_deps = GetClassifiedDeps();
// The input dependencies will be an order-only dependency. This will cause
// Ninja to make sure the inputs are up to date before compiling this source,
// but changes in the inputs deps won't cause the file to be recompiled. See
// the comment on NinjaCBinaryTargetWriter::Run for more detailed explanation.
NinjaTargetWriter::InputDeps stamp_deps = WriteInputDepsStampOrPhonyAndGetDep(
std::vector<const Target*>(), num_output_uses);
std::vector<OutputFile> order_only_deps = stamp_deps.order_only;
std::copy(input_deps.begin(), input_deps.end(),
std::back_inserter(order_only_deps));
// Build lists which will go into different bits of the rustc command line.
// Public rust_library deps go in a --extern rlibs, public non-rust deps go in
// -Ldependency. Also assemble a list of extra (i.e. implicit) deps
// for ninja dependency tracking.
UniqueVector<OutputFile> implicit_deps;
implicit_deps.Append(stamp_deps.implicit.begin(), stamp_deps.implicit.end());
AppendSourcesAndInputsToImplicitDeps(&implicit_deps);
implicit_deps.Append(classified_deps.extra_object_files.begin(),
classified_deps.extra_object_files.end());
if (auto phony = tool_->inputs_phony_or_file(rule_prefix_,
*settings_->build_settings());
phony) {
implicit_deps.emplace_back(std::move(*phony));
}
std::vector<OutputFile> rustdeps;
std::vector<OutputFile> nonrustdeps;
std::vector<OutputFile> swiftmodules;
nonrustdeps.insert(nonrustdeps.end(),
classified_deps.extra_object_files.begin(),
classified_deps.extra_object_files.end());
for (const auto* framework_dep : classified_deps.framework_deps) {
if (framework_dep->has_dependency_output_file()) {
order_only_deps.push_back(framework_dep->dependency_output_file());
}
}
if (target_->IsFinal()) {
for (const Target* dep : classified_deps.swiftmodule_deps) {
swiftmodules.push_back(dep->swift_values().module_output_file());
order_only_deps.push_back(dep->swift_values().module_output_file());
}
}
for (const auto* non_linkable_dep : classified_deps.non_linkable_deps) {
if (non_linkable_dep->has_dependency_output()) {
if (non_linkable_dep->source_types_used().RustSourceUsed() &&
non_linkable_dep->output_type() != Target::SOURCE_SET) {
rustdeps.push_back(non_linkable_dep->dependency_output());
}
}
}
std::vector<OutputFile> group_order_only_deps =
GetOrderOnlyDepsFromNonLinkableDeps(classified_deps.non_linkable_deps);
order_only_deps.insert(order_only_deps.end(), group_order_only_deps.begin(),
group_order_only_deps.end());
for (const auto* linkable_dep : classified_deps.linkable_deps) {
// Rust cdylibs are treated as non-Rust dependencies for linking purposes.
if (linkable_dep->source_types_used().RustSourceUsed() &&
linkable_dep->rust_values().crate_type() != RustValues::CRATE_CDYLIB) {
rustdeps.push_back(linkable_dep->link_output_file());
} else {
nonrustdeps.push_back(linkable_dep->link_output_file());
}
CHECK(linkable_dep->has_dependency_output_file());
implicit_deps.push_back(linkable_dep->dependency_output_file());
}
// Rust libraries specified by paths.
for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
const ConfigValues& cur = iter.cur();
for (const auto& e : cur.externs()) {
if (e.second.is_source_file()) {
implicit_deps.push_back(
OutputFile(settings_->build_settings(), e.second.source_file()));
}
}
}
// Collect the full transitive set of rust libraries that this target depends
// on, and the public flag represents if the target has direct access to the
// dependency through a chain of public_deps.
std::vector<ExternCrate> transitive_crates;
for (const auto& inherited : resolved().GetRustInheritedLibraries(target_)) {
const Target* dep = inherited.target();
bool has_direct_access = inherited.is_public();
// We will tell rustc to look for crate metadata for any rust crate
// dependencies except cdylibs, as they have no metadata present.
if (dep->source_types_used().RustSourceUsed() &&
RustValues::IsRustLibrary(dep)) {
transitive_crates.push_back({dep, has_direct_access});
// If the current crate can directly acccess the `dep` crate, then the
// current crate needs an implicit dependency on `dep` so it will be
// rebuilt if `dep` changes.
if (has_direct_access) {
implicit_deps.push_back(dep->dependency_output_file());
}
}
}
// rustc only links rlibs reachable through the Rust crate graph (via
// --extern / -Ldependency). Rust libraries reachable only through a non-Rust
// (C++) dependency are otherwise dropped from a rustc-driven link, which
// breaks cxx bridges whose Rust half lives behind a C++ target (e.g. //base's
// rust_logger or serde_json_lenient). The C++ linker path handles this via
// the "rlibs" ninja variable (see NinjaCBinaryTargetWriter); mirror it here
// by linking those rlibs directly as archives through the linker. Crates
// already covered by the crate graph above are skipped to avoid linking them
// twice.
if (target_->IsFinal()) {
// Compute the set of rlibs rustc links by itself: the crates passed to
// --extern (those with direct access), plus everything reachable from them
// by following Rust crate dependencies (rustc resolves these through crate
// metadata). A Rust library is NOT in this set if it can only be reached
// through a non-Rust (C++) dependency, since the C++ boundary breaks the
// crate graph.
std::unordered_set<const Target*> linked_by_rustc;
std::vector<const Target*> work;
for (const auto& crate : transitive_crates) {
if (crate.has_direct_access)
work.push_back(crate.target);
}
while (!work.empty()) {
const Target* crate = work.back();
work.pop_back();
if (!linked_by_rustc.insert(crate).second)
continue;
for (const auto& pair : crate->GetDeps(Target::DEPS_LINKED)) {
// Groups are flattened into the crate graph, so rustc links rlibs
// reachable through them; treat groups as transparent in this walk so
// those rlibs aren't passed to the linker a second time below.
if (pair.ptr->output_type() == Target::GROUP ||
(pair.ptr->source_types_used().RustSourceUsed() &&
RustValues::IsRustLibrary(pair.ptr)))
work.push_back(pair.ptr);
}
}
// Link any Rust library in the transitive closure that rustc won't link
// itself directly as an archive, mirroring the "rlibs" handling in
// NinjaCBinaryTargetWriter. This is what lets cxx bridges whose Rust half
// sits behind a C++ target (e.g. //base's rust_logger) resolve.
for (const auto& inherited : resolved().GetInheritedLibraries(target_)) {
const Target* dep = inherited.target();
if (dep->output_type() == Target::RUST_LIBRARY &&
!linked_by_rustc.contains(dep)) {
CHECK(dep->has_dependency_output_file());
nonrustdeps.push_back(dep->dependency_output_file());
implicit_deps.push_back(dep->dependency_output_file());
}
}
}
std::vector<OutputFile> tool_outputs;
SubstitutionWriter::ApplyListToLinkerAsOutputFile(
target_, tool_, tool_->outputs(), &tool_outputs);
WriteCompilerBuildLine({target_->rust_values().crate_root()},
implicit_deps.vector(), order_only_deps, tool_,
tool_outputs);
std::vector<const Target*> extern_deps(
classified_deps.linkable_deps.vector());
std::copy(classified_deps.non_linkable_deps.begin(),
classified_deps.non_linkable_deps.end(),
std::back_inserter(extern_deps));
WriteExternsAndDeps(extern_deps, transitive_crates, rustdeps, nonrustdeps,
swiftmodules);
WriteSourcesAndInputs();
}
void NinjaRustBinaryTargetWriter::WriteCompilerVars() {
const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
EscapeOptions opts = GetFlagOptions();
WriteCrateVars(target_, tool_, opts, target_group_.target_vars);
WriteRustCompilerVars(subst, /*always_write=*/true);
WriteSharedVars(subst);
}
void NinjaRustBinaryTargetWriter::AppendSourcesAndInputsToImplicitDeps(
UniqueVector<OutputFile>* deps) const {
// Only the crate_root file needs to be given to rustc as input.
// Any other 'sources' are just implicit deps.
// Most Rust targets won't bother specifying the "sources =" line
// because it is handled sufficiently by crate_root and the generation
// of depfiles by rustc. But for those which do...
for (const auto& source : target_->sources()) {
deps->emplace_back(settings_->build_settings(), source);
}
for (const auto& data : target_->config_values().inputs()) {
deps->emplace_back(settings_->build_settings(), data);
}
}
void NinjaRustBinaryTargetWriter::WriteSourcesAndInputs() {
std::ostringstream val;
for (const auto& source : target_->sources()) {
val << " ";
path_output_.WriteFile(val,
OutputFile(settings_->build_settings(), source));
}
for (const auto& data : target_->config_values().inputs()) {
val << " ";
path_output_.WriteFile(val, OutputFile(settings_->build_settings(), data));
}
target_group_.edges.back().edge_vars.emplace_back("sources", val.str());
}
void NinjaRustBinaryTargetWriter::WriteExternsAndDeps(
const std::vector<const Target*>& deps,
const std::vector<ExternCrate>& transitive_rust_deps,
const std::vector<OutputFile>& rustdeps,
const std::vector<OutputFile>& nonrustdeps,
const std::vector<OutputFile>& swiftmodules) {
std::ostringstream externs_val;
// Writes a external LibFile which comes from user-specified externs, and may
// be either a string or a SourceFile.
auto write_extern_lib_file = [this, &externs_val](std::string_view crate_name,
LibFile lib_file) {
externs_val << " --extern ";
externs_val << crate_name;
externs_val << "=";
if (lib_file.is_source_file()) {
path_output_.WriteFile(externs_val, lib_file.source_file());
} else {
EscapeOptions escape_opts_command;
escape_opts_command.mode = ESCAPE_NINJA_COMMAND;
EscapeStringToStream(externs_val, lib_file.value(), escape_opts_command);
}
};
// Writes an external OutputFile which comes from a dependency of the current
// target.
auto write_extern_target = [this, &externs_val](const Target& dep) {
std::string_view crate_name;
const auto& aliased_deps = target_->rust_values().aliased_deps();
if (auto it = aliased_deps.find(dep.label()); it != aliased_deps.end()) {
crate_name = it->second;
} else {
crate_name = dep.rust_values().crate_name();
}
externs_val << " --extern ";
externs_val << crate_name;
externs_val << "=";
path_output_.WriteFile(externs_val, dep.dependency_output_file());
};
// Tracking to avoid emitted the same lib twice. We track it instead of
// pre-emptively constructing a UniqueVector since we would have to also store
// the crate name, and in the future the public-ness.
std::unordered_set<OutputFile> emitted_rust_libs;
// TODO: We defer private dependencies to -Ldependency until --extern priv is
// stabilized.
UniqueVector<SourceDir> private_extern_dirs;
// Walk the transitive closure of all rust dependencies.
//
// For dependencies that are meant to be accessible we pass them to --extern
// in order to add them to the crate's extern prelude.
//
// For all transitive dependencies, we add them to `private_extern_dirs` in
// order to generate a -Ldependency switch that points to them. This ensures
// that rustc can find them if they are used by other dependencies. For
// example:
//
// A -> C --public--> D
// -> B --private-> D
//
// Here A has direct access to D, but B and C also make use of D, and they
// will only search the paths specified to -Ldependency, thus D needs to
// appear as both a --extern (for A) and -Ldependency (for B and C).
for (const auto& crate : transitive_rust_deps) {
const OutputFile& rust_lib = crate.target->dependency_output_file();
if (emitted_rust_libs.count(rust_lib) == 0) {
if (crate.has_direct_access) {
write_extern_target(*crate.target);
}
emitted_rust_libs.insert(rust_lib);
}
private_extern_dirs.push_back(
rust_lib.AsSourceFile(settings_->build_settings()).GetDir());
}
// Add explicitly specified externs from the GN target.
for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
const ConfigValues& cur = iter.cur();
for (const auto& [crate_name, lib_file] : cur.externs()) {
write_extern_lib_file(crate_name, lib_file);
}
}
target_group_.edges.back().edge_vars.emplace_back("externs",
externs_val.str());
std::ostringstream rustdeps_val;
for (const SourceDir& dir : private_extern_dirs) {
// TODO: switch to using `--extern priv:name` after stabilization.
rustdeps_val << " -Ldependency=";
path_output_.WriteDir(rustdeps_val, dir, PathOutput::DIR_NO_LAST_SLASH);
}
// If rustc will invoke a linker, then pass linker arguments to include those
// non-Rust native dependencies in the linking step.
// Before outputting any libraries to link, ensure the linker is in a mode
// that allows dynamic linking, as rustc may have previously put it into
// static-only mode.
if (nonrustdeps.size() > 0) {
rustdeps_val << " " << tool_->dynamic_link_switch();
}
for (const auto& nonrustdep : nonrustdeps) {
rustdeps_val << " -Clink-arg=";
path_output_.WriteFile(rustdeps_val, nonrustdep);
}
// Library search paths are required to find system libraries named in #[link]
// directives, which will not be specified in non-Rust native dependencies.
WriteLibrarySearchPath(rustdeps_val, tool_);
// If rustc will invoke a linker, all libraries need the passed through to the
// linker.
WriteLibs(rustdeps_val, tool_);
WriteFrameworks(rustdeps_val, tool_);
WriteSwiftModules(rustdeps_val, tool_, swiftmodules);
target_group_.edges.back().edge_vars.emplace_back("rustdeps",
rustdeps_val.str());
std::ostringstream ldflags_val;
// If rustc will invoke a linker, linker flags need to be forwarded through to
// the linker.
WriteCustomLinkerFlags(ldflags_val, tool_);
target_group_.edges.back().edge_vars.emplace_back("ldflags",
ldflags_val.str());
}