blob: 355a8194e5c91f15ce41d93303a132f6a53030cb [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "rs_bindings_from_cc/cmdline.h"
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "absl/flags/flag.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/substitute.h"
#include "common/ffi_types.h"
#include "common/status_macros.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
ABSL_FLAG(bool, do_nothing, false,
"if set to true the tool will produce empty files "
"(useful for testing Bazel integration)");
ABSL_FLAG(std::string, rs_out, "",
"output path for the Rust source file with bindings");
ABSL_FLAG(std::string, cc_out, "",
"output path for the C++ source file with bindings implementation");
ABSL_FLAG(std::string, ir_out, "",
"(optional) output path for the JSON IR. If not present, the JSON IR "
"will not be dumped.");
ABSL_FLAG(std::string, crubit_support_path_format, "",
"the format of `#include` for including Crubit C++ support library "
"headers in the "
"generated .cc files, in the format specifier, use `{header}` as the "
"placeholder. For "
"example, to include `support_header.h` as "
"`#include <crubit/support/support_header.h>, specify "
"`<crubit/support/{header}>`; for "
"`#include \"crubit/support/support_header.h\", specify "
"`\"crubit/support/{header}`,");
ABSL_FLAG(std::string, clang_format_exe_path, "",
"Path to a clang-format executable that will be used to format the "
".cc files generated by the tool.");
ABSL_FLAG(std::string, rustfmt_exe_path, "",
"Path to a rustfmt executable that will be used to format the "
".rs files generated by the tool.");
ABSL_FLAG(std::string, rustfmt_config_path, "",
"(optional) path to a rustfmt.toml file that should replace the "
"default formatting of the .rs files generated by the tool.");
ABSL_FLAG(std::vector<std::string>, public_headers, std::vector<std::string>(),
"public headers of the cc_library this tool should generate bindings "
"for, in a format suitable for usage in google3-relative quote "
"include (#include \"\").");
ABSL_FLAG(std::string, target, "", "The target to generate bindings for.");
ABSL_FLAG(std::string, target_args, "",
"Per-target Crubit arguments, encoded as a JSON array. This contains "
"both the list of headers assigned to the target (h), and the set of "
"enabled features (f). For example:"
"[\n"
" {\n"
" \"t\": \"//foo/bar:baz\",\n"
" \"h\": [\"foo/bar/header1.h\", \"foo/bar/header2.h\"],\n"
" \"f\": [\"supported\"]\n"
" },\n"
"...\n"
"]");
ABSL_FLAG(std::vector<std::string>, extra_rs_srcs, std::vector<std::string>(),
"Additional Rust source files to include into the crate.");
ABSL_FLAG(std::vector<std::string>, srcs_to_scan_for_instantiations,
std::vector<std::string>(),
"[template instantiation mode only] all Rust source files of a crate "
"for which we are instantiating templates.");
ABSL_FLAG(std::string, instantiations_out, "",
"[template instantiation mode only] output path for the JSON file "
"with mapping from a template instantiation to a generated Rust "
"struct name. This file is used by cc_template! macro expansion.");
ABSL_FLAG(std::string, namespaces_out, "",
"(optional) output path for the JSON file containing the target's"
"namespace hierarchy.");
ABSL_FLAG(std::string, error_report_out, "",
"(optional) output path for the JSON error report");
ABSL_FLAG(bool, generate_source_location_in_doc_comment, true,
"add the source code location from which the binding originates in"
"the doc comment of the binding");
namespace crubit {
namespace {
struct TargetArgs {
std::string target;
std::vector<std::string> headers;
std::vector<std::string> features;
};
bool fromJSON(const llvm::json::Value& json, TargetArgs& out,
llvm::json::Path path) {
llvm::json::ObjectMapper mapper(json, path);
return mapper && mapper.map("t", out.target) &&
mapper.mapOptional("h", out.headers) &&
mapper.mapOptional("f", out.features);
}
} // namespace
absl::StatusOr<Cmdline> Cmdline::Create() {
return CreateFromArgs(
absl::GetFlag(FLAGS_target), absl::GetFlag(FLAGS_cc_out),
absl::GetFlag(FLAGS_rs_out), absl::GetFlag(FLAGS_ir_out),
absl::GetFlag(FLAGS_namespaces_out),
absl::GetFlag(FLAGS_crubit_support_path_format),
absl::GetFlag(FLAGS_clang_format_exe_path),
absl::GetFlag(FLAGS_rustfmt_exe_path),
absl::GetFlag(FLAGS_rustfmt_config_path), absl::GetFlag(FLAGS_do_nothing),
absl::GetFlag(FLAGS_public_headers), absl::GetFlag(FLAGS_target_args),
absl::GetFlag(FLAGS_extra_rs_srcs),
absl::GetFlag(FLAGS_srcs_to_scan_for_instantiations),
absl::GetFlag(FLAGS_instantiations_out),
absl::GetFlag(FLAGS_error_report_out),
absl::GetFlag(FLAGS_generate_source_location_in_doc_comment)
? SourceLocationDocComment::Enabled
: SourceLocationDocComment::Disabled);
}
absl::StatusOr<Cmdline> Cmdline::CreateFromArgs(
std::string current_target, std::string cc_out, std::string rs_out,
std::string ir_out, std::string namespaces_out,
std::string crubit_support_path_format, std::string clang_format_exe_path,
std::string rustfmt_exe_path, std::string rustfmt_config_path,
bool do_nothing, std::vector<std::string> public_headers,
std::string target_args_str, std::vector<std::string> extra_rs_srcs,
std::vector<std::string> srcs_to_scan_for_instantiations,
std::string instantiations_out, std::string error_report_out,
SourceLocationDocComment generate_source_location_in_doc_comment) {
Cmdline cmdline;
if (current_target.empty()) {
return absl::InvalidArgumentError("please specify --target");
}
cmdline.current_target_ = BazelLabel(std::move(current_target));
if (rs_out.empty()) {
return absl::InvalidArgumentError("please specify --rs_out");
}
cmdline.rs_out_ = std::move(rs_out);
if (cc_out.empty()) {
return absl::InvalidArgumentError("please specify --cc_out");
}
cmdline.cc_out_ = std::move(cc_out);
cmdline.ir_out_ = std::move(ir_out);
cmdline.namespaces_out_ = std::move(namespaces_out);
if (crubit_support_path_format.empty()) {
return absl::InvalidArgumentError(
"please specify --crubit_support_path_format");
} else if (!absl::StrContains(crubit_support_path_format, "{header}")) {
return absl::InvalidArgumentError(
"cannot find `{header}` placeholder in crubit_support_path_format");
}
cmdline.crubit_support_path_format_ = std::move(crubit_support_path_format);
if (clang_format_exe_path.empty()) {
return absl::InvalidArgumentError("please specify --clang_format_exe_path");
}
cmdline.clang_format_exe_path_ = std::move(clang_format_exe_path);
if (rustfmt_exe_path.empty()) {
return absl::InvalidArgumentError("please specify --rustfmt_exe_path");
}
cmdline.rustfmt_exe_path_ = std::move(rustfmt_exe_path);
cmdline.rustfmt_config_path_ = std::move(rustfmt_config_path);
cmdline.do_nothing_ = do_nothing;
cmdline.generate_source_location_in_doc_comment_ =
generate_source_location_in_doc_comment;
if (public_headers.empty()) {
return absl::InvalidArgumentError("please specify --public_headers");
}
std::transform(public_headers.begin(), public_headers.end(),
std::back_inserter(cmdline.public_headers_),
[](const std::string& s) { return HeaderName(s); });
cmdline.extra_rs_srcs_ = std::move(extra_rs_srcs);
if (srcs_to_scan_for_instantiations.empty() != instantiations_out.empty()) {
return absl::InvalidArgumentError(
"please specify both --rust_sources and --instantiations_out when "
"requesting a template instantiation mode");
}
cmdline.instantiations_out_ = std::move(instantiations_out);
cmdline.srcs_to_scan_for_instantiations_ =
std::move(srcs_to_scan_for_instantiations);
cmdline.error_report_out_ = std::move(error_report_out);
if (target_args_str.empty()) {
return absl::InvalidArgumentError("please specify --target_args");
}
auto target_args =
llvm::json::parse<std::vector<TargetArgs>>(std::move(target_args_str));
if (auto err = target_args.takeError()) {
return absl::InvalidArgumentError(absl::StrCat(
"Malformed `--target_args` argument: ", toString(std::move(err))));
}
for (const TargetArgs& it : *target_args) {
const std::string& target = it.target;
if (target.empty()) {
return absl::InvalidArgumentError(
"Expected `t` fields of `--target_args` to be a non-empty "
"string");
}
for (const std::string& header : it.headers) {
if (header.empty()) {
return absl::InvalidArgumentError(
"Expected `h` (header) fields of `--target_args` to be an "
"array of non-empty strings");
}
BazelLabel target_label(target);
auto [it, inserted] = cmdline.headers_to_targets_.try_emplace(
HeaderName(header), std::move(target_label));
if (!inserted) {
LOG(WARNING) << "The `--target_args` cmdline argument assigns `"
<< header << "` header to two conflicting targets: `"
<< target << "` vs `" << it->second.value() << "`";
// Assign the one that comes first alphabetically, to get a consistent
// result.
if (target_label.value() < it->second.value()) {
it->second = std::move(target_label);
}
}
}
for (const std::string& feature : it.features) {
if (feature.empty()) {
return absl::InvalidArgumentError(
"Expected `f` (feature) fields of `--target_args` to be an "
"array of non-empty strings");
}
cmdline.target_to_features_[BazelLabel(target)].insert(feature);
}
}
for (const HeaderName& public_header : cmdline.public_headers_) {
CRUBIT_RETURN_IF_ERROR(cmdline.FindHeader(public_header).status());
}
return cmdline;
}
absl::StatusOr<BazelLabel> Cmdline::FindHeader(const HeaderName& header) const {
auto it = headers_to_targets_.find(header);
if (it == headers_to_targets_.end()) {
return absl::InvalidArgumentError(absl::Substitute(
"Couldn't find header '$0' in the `headers_to_target` map "
"derived from the --target_args cmdline argument",
header.IncludePath()));
}
return it->second;
}
Cmdline::Cmdline() = default;
} // namespace crubit