| // 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 |