blob: 50a654552e91eef7aefbd5da2454003d19304375 [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/strings/str_cat.h"
#include "absl/strings/substitute.h"
#include "common/status_macros.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, "",
"path to a the crubit/rs_bindings/support directory in a format that "
"should be used in the #include directives inside the generated .cc "
"files.");
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, targets_and_headers, std::string(),
"Information about which headers belong to which targets, encoded as "
"a JSON array. For example: "
"[\n"
" {\n"
" \"t\": \"//foo/bar:baz\",\n"
" \"h\": [\"foo/bar/header1.h\", \"foo/bar/header2.h\"]\n"
" },\n"
"...\n"
"]");
ABSL_FLAG(std::vector<std::string>, rust_sources, 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.");
namespace crubit {
namespace {
struct TargetAndHeaders {
std::string target;
std::vector<std::string> headers;
};
bool fromJSON(const llvm::json::Value& json, TargetAndHeaders& out,
llvm::json::Path path) {
llvm::json::ObjectMapper mapper(json, path);
return mapper && mapper.map("t", out.target) && mapper.map("h", out.headers);
}
} // namespace
absl::StatusOr<Cmdline> Cmdline::Create() {
return CreateFromArgs(
absl::GetFlag(FLAGS_cc_out), absl::GetFlag(FLAGS_rs_out),
absl::GetFlag(FLAGS_ir_out), absl::GetFlag(FLAGS_crubit_support_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_targets_and_headers),
absl::GetFlag(FLAGS_rust_sources),
absl::GetFlag(FLAGS_instantiations_out));
}
absl::StatusOr<Cmdline> Cmdline::CreateFromArgs(
std::string cc_out, std::string rs_out, std::string ir_out,
std::string crubit_support_path, std::string rustfmt_exe_path,
std::string rustfmt_config_path, bool do_nothing,
std::vector<std::string> public_headers,
std::string targets_and_headers_str, std::vector<std::string> rust_sources,
std::string instantiations_out) {
Cmdline cmdline;
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);
if (crubit_support_path.empty()) {
return absl::InvalidArgumentError("please specify --crubit_support_path");
}
cmdline.crubit_support_path_ = std::move(crubit_support_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;
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); });
if (rust_sources.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.rust_sources_ = std::move(rust_sources);
if (targets_and_headers_str.empty()) {
return absl::InvalidArgumentError("please specify --targets_and_headers");
}
auto targets_and_headers = llvm::json::parse<std::vector<TargetAndHeaders>>(
std::move(targets_and_headers_str));
if (auto err = targets_and_headers.takeError()) {
return absl::InvalidArgumentError(
absl::StrCat("Malformed `--targets_and_headers` argument: ",
toString(std::move(err))));
}
for (const TargetAndHeaders& it : *targets_and_headers) {
const std::string& target = it.target;
if (target.empty()) {
return absl::InvalidArgumentError(
"Expected `t` fields of `--targets_and_headers` to be a non-empty "
"string");
}
for (const std::string& header : it.headers) {
if (header.empty()) {
return absl::InvalidArgumentError(
"Expected `h` fields of `--targets_and_headers` to be an array of "
"non-empty strings");
}
const auto [it, inserted] = cmdline.headers_to_targets_.insert(
std::make_pair(HeaderName(header), BazelLabel(target)));
if (!inserted) {
return absl::InvalidArgumentError(absl::Substitute(
"The `--targets_and_headers` cmdline argument assigns "
"`$0` header to two conflicting targets: `$1` vs `$2`",
header, target, it->second.value()));
}
}
}
CRUBIT_ASSIGN_OR_RETURN(cmdline.current_target_,
cmdline.FindHeader(cmdline.public_headers_[0]));
for (const HeaderName& public_header : cmdline.public_headers_) {
CRUBIT_ASSIGN_OR_RETURN(BazelLabel header_target,
cmdline.FindHeader(public_header));
if (cmdline.current_target_ != header_target) {
return absl::InvalidArgumentError(absl::Substitute(
"Expected all public headers to belong to the current target '$0', "
"but header '$1' belongs to '$2'",
cmdline.current_target_.value(), public_header.IncludePath(),
header_target.value()));
}
}
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 --targets_and_headers cmdline argument",
header.IncludePath()));
}
return it->second;
}
Cmdline::Cmdline() = default;
} // namespace crubit