blob: 461ccafd3b15f8851685f5258e4823f2cd631261 [file] [log] [blame]
Lukasz Anforowicz016eb362022-03-02 18:37:03 +00001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5#include "rs_bindings_from_cc/cmdline.h"
6
7#include <algorithm>
8#include <iterator>
9#include <string>
10#include <utility>
11#include <vector>
12
Lukasz Anforowiczcec7a8a2022-04-27 10:24:51 -070013#include "absl/flags/flag.h"
14#include "absl/strings/str_cat.h"
15#include "absl/strings/substitute.h"
Marco Polettic61bcc42022-04-08 12:54:30 -070016#include "common/status_macros.h"
Lukasz Anforowiczcec7a8a2022-04-27 10:24:51 -070017#include "llvm/Support/JSON.h"
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000018
19ABSL_FLAG(bool, do_nothing, false,
20 "if set to true the tool will produce empty files "
Googler6c3de122022-03-28 11:40:41 +000021 "(useful for testing Bazel integration)");
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000022ABSL_FLAG(std::string, rs_out, "",
23 "output path for the Rust source file with bindings");
24ABSL_FLAG(std::string, cc_out, "",
25 "output path for the C++ source file with bindings implementation");
26ABSL_FLAG(std::string, ir_out, "",
27 "(optional) output path for the JSON IR. If not present, the JSON IR "
28 "will not be dumped.");
Lukasz Anforowiczdd907702022-05-06 09:24:07 -070029ABSL_FLAG(std::string, crubit_support_path, "",
30 "path to a the crubit/rs_bindings/support directory in a format that "
31 "should be used in the #include directives inside the generated .cc "
32 "files.");
Lukasz Anforowiczd7d68f02022-05-26 07:41:02 -070033ABSL_FLAG(std::string, rustfmt_exe_path, "",
34 "Path to a rustfmt executable that will be used to format the "
35 ".rs files generated by the tool.");
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -070036ABSL_FLAG(std::string, rustfmt_config_path, "",
37 "(optional) path to a rustfmt.toml file that should replace the "
38 "default formatting of the .rs files generated by the tool.");
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000039ABSL_FLAG(std::vector<std::string>, public_headers, std::vector<std::string>(),
40 "public headers of the cc_library this tool should generate bindings "
41 "for, in a format suitable for usage in google3-relative quote "
42 "include (#include \"\").");
43ABSL_FLAG(std::string, targets_and_headers, std::string(),
44 "Information about which headers belong to which targets, encoded as "
45 "a JSON array. For example: "
46 "[\n"
47 " {\n"
48 " \"t\": \"//foo/bar:baz\",\n"
49 " \"h\": [\"foo/bar/header1.h\", \"foo/bar/header2.h\"]\n"
50 " },\n"
51 "...\n"
52 "]");
Devin Jeanpierre96bf0bd2022-10-04 20:32:15 -070053ABSL_FLAG(std::vector<std::string>, extra_rs_srcs, std::vector<std::string>(),
54 "Additional Rust source files to include into the crate.");
55ABSL_FLAG(std::vector<std::string>, srcs_to_scan_for_instantiations,
56 std::vector<std::string>(),
Marcel Hlopko18bb5d22022-05-09 04:23:43 -070057 "[template instantiation mode only] all Rust source files of a crate "
58 "for which we are instantiating templates.");
59ABSL_FLAG(std::string, instantiations_out, "",
60 "[template instantiation mode only] output path for the JSON file "
61 "with mapping from a template instantiation to a generated Rust "
62 "struct name. This file is used by cc_template! macro expansion.");
Rosica Dejanovskaabe406f2022-09-02 07:06:50 -070063ABSL_FLAG(std::string, namespaces_out, "",
64 "(optional) output path for the JSON file containing the target's"
65 "namespace hierarchy.");
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000066
Marcel Hlopkof15e8ce2022-04-08 08:46:09 -070067namespace crubit {
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000068
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +000069namespace {
70
71struct TargetAndHeaders {
72 std::string target;
73 std::vector<std::string> headers;
74};
75
76bool fromJSON(const llvm::json::Value& json, TargetAndHeaders& out,
77 llvm::json::Path path) {
78 llvm::json::ObjectMapper mapper(json, path);
79 return mapper && mapper.map("t", out.target) && mapper.map("h", out.headers);
80}
81
82} // namespace
83
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000084absl::StatusOr<Cmdline> Cmdline::Create() {
85 return CreateFromArgs(
86 absl::GetFlag(FLAGS_cc_out), absl::GetFlag(FLAGS_rs_out),
Rosica Dejanovskaabe406f2022-09-02 07:06:50 -070087 absl::GetFlag(FLAGS_ir_out), absl::GetFlag(FLAGS_namespaces_out),
88 absl::GetFlag(FLAGS_crubit_support_path),
Lukasz Anforowiczd7d68f02022-05-26 07:41:02 -070089 absl::GetFlag(FLAGS_rustfmt_exe_path),
Lukasz Anforowiczdd907702022-05-06 09:24:07 -070090 absl::GetFlag(FLAGS_rustfmt_config_path), absl::GetFlag(FLAGS_do_nothing),
91 absl::GetFlag(FLAGS_public_headers),
Marcel Hlopko18bb5d22022-05-09 04:23:43 -070092 absl::GetFlag(FLAGS_targets_and_headers),
Devin Jeanpierre96bf0bd2022-10-04 20:32:15 -070093 absl::GetFlag(FLAGS_extra_rs_srcs),
94 absl::GetFlag(FLAGS_srcs_to_scan_for_instantiations),
Marcel Hlopko18bb5d22022-05-09 04:23:43 -070095 absl::GetFlag(FLAGS_instantiations_out));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000096}
97
98absl::StatusOr<Cmdline> Cmdline::CreateFromArgs(
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -070099 std::string cc_out, std::string rs_out, std::string ir_out,
Rosica Dejanovskaabe406f2022-09-02 07:06:50 -0700100 std::string namespaces_out, std::string crubit_support_path,
101 std::string rustfmt_exe_path, std::string rustfmt_config_path,
102 bool do_nothing, std::vector<std::string> public_headers,
Devin Jeanpierre96bf0bd2022-10-04 20:32:15 -0700103 std::string targets_and_headers_str, std::vector<std::string> extra_rs_srcs,
104 std::vector<std::string> srcs_to_scan_for_instantiations,
Marcel Hlopko18bb5d22022-05-09 04:23:43 -0700105 std::string instantiations_out) {
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000106 Cmdline cmdline;
107
108 if (rs_out.empty()) {
109 return absl::InvalidArgumentError("please specify --rs_out");
110 }
111 cmdline.rs_out_ = std::move(rs_out);
112
113 if (cc_out.empty()) {
114 return absl::InvalidArgumentError("please specify --cc_out");
115 }
116 cmdline.cc_out_ = std::move(cc_out);
117
118 cmdline.ir_out_ = std::move(ir_out);
Lukasz Anforowiczdd907702022-05-06 09:24:07 -0700119
Rosica Dejanovskaabe406f2022-09-02 07:06:50 -0700120 cmdline.namespaces_out_ = std::move(namespaces_out);
121
Lukasz Anforowiczdd907702022-05-06 09:24:07 -0700122 if (crubit_support_path.empty()) {
123 return absl::InvalidArgumentError("please specify --crubit_support_path");
124 }
125 cmdline.crubit_support_path_ = std::move(crubit_support_path);
126
Lukasz Anforowiczd7d68f02022-05-26 07:41:02 -0700127 if (rustfmt_exe_path.empty()) {
128 return absl::InvalidArgumentError("please specify --rustfmt_exe_path");
129 }
130 cmdline.rustfmt_exe_path_ = std::move(rustfmt_exe_path);
131
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -0700132 cmdline.rustfmt_config_path_ = std::move(rustfmt_config_path);
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000133 cmdline.do_nothing_ = do_nothing;
134
135 if (public_headers.empty()) {
136 return absl::InvalidArgumentError("please specify --public_headers");
137 }
138 std::transform(public_headers.begin(), public_headers.end(),
139 std::back_inserter(cmdline.public_headers_),
140 [](const std::string& s) { return HeaderName(s); });
141
Devin Jeanpierre96bf0bd2022-10-04 20:32:15 -0700142 cmdline.extra_rs_srcs_ = std::move(extra_rs_srcs);
143
144 if (srcs_to_scan_for_instantiations.empty() != instantiations_out.empty()) {
Marcel Hlopko18bb5d22022-05-09 04:23:43 -0700145 return absl::InvalidArgumentError(
146 "please specify both --rust_sources and --instantiations_out when "
147 "requesting a template instantiation mode");
148 }
149 cmdline.instantiations_out_ = std::move(instantiations_out);
Devin Jeanpierre96bf0bd2022-10-04 20:32:15 -0700150 cmdline.srcs_to_scan_for_instantiations_ =
151 std::move(srcs_to_scan_for_instantiations);
Marcel Hlopko18bb5d22022-05-09 04:23:43 -0700152
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000153 if (targets_and_headers_str.empty()) {
154 return absl::InvalidArgumentError("please specify --targets_and_headers");
155 }
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000156 auto targets_and_headers = llvm::json::parse<std::vector<TargetAndHeaders>>(
157 std::move(targets_and_headers_str));
158 if (auto err = targets_and_headers.takeError()) {
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000159 return absl::InvalidArgumentError(
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000160 absl::StrCat("Malformed `--targets_and_headers` argument: ",
161 toString(std::move(err))));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000162 }
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000163 for (const TargetAndHeaders& it : *targets_and_headers) {
164 const std::string& target = it.target;
165 if (target.empty()) {
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000166 return absl::InvalidArgumentError(
167 "Expected `t` fields of `--targets_and_headers` to be a non-empty "
168 "string");
169 }
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000170 for (const std::string& header : it.headers) {
171 if (header.empty()) {
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000172 return absl::InvalidArgumentError(
173 "Expected `h` fields of `--targets_and_headers` to be an array of "
174 "non-empty strings");
175 }
176 const auto [it, inserted] = cmdline.headers_to_targets_.insert(
Googler6c3de122022-03-28 11:40:41 +0000177 std::make_pair(HeaderName(header), BazelLabel(target)));
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000178 if (!inserted) {
179 return absl::InvalidArgumentError(absl::Substitute(
180 "The `--targets_and_headers` cmdline argument assigns "
181 "`$0` header to two conflicting targets: `$1` vs `$2`",
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000182 header, target, it->second.value()));
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000183 }
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000184 }
185 }
186
Lukasz Anforowiczd41a4e92022-03-17 17:08:57 +0000187 CRUBIT_ASSIGN_OR_RETURN(cmdline.current_target_,
188 cmdline.FindHeader(cmdline.public_headers_[0]));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000189 for (const HeaderName& public_header : cmdline.public_headers_) {
Googler6c3de122022-03-28 11:40:41 +0000190 CRUBIT_ASSIGN_OR_RETURN(BazelLabel header_target,
Lukasz Anforowiczd41a4e92022-03-17 17:08:57 +0000191 cmdline.FindHeader(public_header));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000192
193 if (cmdline.current_target_ != header_target) {
194 return absl::InvalidArgumentError(absl::Substitute(
195 "Expected all public headers to belong to the current target '$0', "
196 "but header '$1' belongs to '$2'",
197 cmdline.current_target_.value(), public_header.IncludePath(),
198 header_target.value()));
199 }
200 }
201
202 return cmdline;
203}
204
Googler6c3de122022-03-28 11:40:41 +0000205absl::StatusOr<BazelLabel> Cmdline::FindHeader(const HeaderName& header) const {
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000206 auto it = headers_to_targets_.find(header);
207 if (it == headers_to_targets_.end()) {
208 return absl::InvalidArgumentError(absl::Substitute(
209 "Couldn't find header '$0' in the `headers_to_target` map "
210 "derived from the --targets_and_headers cmdline argument",
211 header.IncludePath()));
212 }
213 return it->second;
214}
215
216Cmdline::Cmdline() = default;
217
Marcel Hlopkof15e8ce2022-04-08 08:46:09 -0700218} // namespace crubit