blob: eaf93579ad5065259044ae75e4098ce8bb96ea17 [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
13#include "third_party/absl/flags/flag.h"
14#include "third_party/absl/strings/substitute.h"
15#include "third_party/json/include/nlohmann/json.hpp"
16#include "util/task/status_macros.h"
17
18ABSL_FLAG(bool, do_nothing, false,
19 "if set to true the tool will produce empty files "
20 "(useful for testing Blaze integration)");
21ABSL_FLAG(std::string, rs_out, "",
22 "output path for the Rust source file with bindings");
23ABSL_FLAG(std::string, cc_out, "",
24 "output path for the C++ source file with bindings implementation");
25ABSL_FLAG(std::string, ir_out, "",
26 "(optional) output path for the JSON IR. If not present, the JSON IR "
27 "will not be dumped.");
28ABSL_FLAG(std::vector<std::string>, public_headers, std::vector<std::string>(),
29 "public headers of the cc_library this tool should generate bindings "
30 "for, in a format suitable for usage in google3-relative quote "
31 "include (#include \"\").");
32ABSL_FLAG(std::string, targets_and_headers, std::string(),
33 "Information about which headers belong to which targets, encoded as "
34 "a JSON array. For example: "
35 "[\n"
36 " {\n"
37 " \"t\": \"//foo/bar:baz\",\n"
38 " \"h\": [\"foo/bar/header1.h\", \"foo/bar/header2.h\"]\n"
39 " },\n"
40 "...\n"
41 "]");
42
43namespace rs_bindings_from_cc {
44
45absl::StatusOr<Cmdline> Cmdline::Create() {
46 return CreateFromArgs(
47 absl::GetFlag(FLAGS_cc_out), absl::GetFlag(FLAGS_rs_out),
48 absl::GetFlag(FLAGS_ir_out), absl::GetFlag(FLAGS_do_nothing),
49 absl::GetFlag(FLAGS_public_headers),
50 absl::GetFlag(FLAGS_targets_and_headers));
51}
52
53absl::StatusOr<Cmdline> Cmdline::CreateFromArgs(
54 std::string cc_out, std::string rs_out, std::string ir_out, bool do_nothing,
55 std::vector<std::string> public_headers,
56 std::string targets_and_headers_str) {
57 Cmdline cmdline;
58
59 if (rs_out.empty()) {
60 return absl::InvalidArgumentError("please specify --rs_out");
61 }
62 cmdline.rs_out_ = std::move(rs_out);
63
64 if (cc_out.empty()) {
65 return absl::InvalidArgumentError("please specify --cc_out");
66 }
67 cmdline.cc_out_ = std::move(cc_out);
68
69 cmdline.ir_out_ = std::move(ir_out);
70 cmdline.do_nothing_ = do_nothing;
71
72 if (public_headers.empty()) {
73 return absl::InvalidArgumentError("please specify --public_headers");
74 }
75 std::transform(public_headers.begin(), public_headers.end(),
76 std::back_inserter(cmdline.public_headers_),
77 [](const std::string& s) { return HeaderName(s); });
78
79 if (targets_and_headers_str.empty()) {
80 return absl::InvalidArgumentError("please specify --targets_and_headers");
81 }
82 nlohmann::json targets_and_headers =
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +000083 nlohmann::json::parse(std::move(targets_and_headers_str),
84 /* cb= */ nullptr,
85 /* allow_exceptions= */ false);
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000086 if (!targets_and_headers.is_array()) {
87 return absl::InvalidArgumentError(
88 "Expected `--targets_and_headers` to be a JSON array of objects");
89 }
90 for (const auto& target_and_headers : targets_and_headers) {
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +000091 if (!target_and_headers.contains("t")) {
92 return absl::InvalidArgumentError(
93 "Missing `t` field in an `--targets_and_headers` object");
94 }
95 if (!target_and_headers["t"].is_string()) {
96 return absl::InvalidArgumentError(
97 "Expected `t` fields of `--targets_and_headers` to be a string");
98 }
99 if (!target_and_headers.contains("h")) {
100 return absl::InvalidArgumentError(
101 "Missing `h` field in an `--targets_and_headers` object");
102 }
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000103 if (!target_and_headers["h"].is_array()) {
104 return absl::InvalidArgumentError(
105 "Expected `h` fields of `--targets_and_headers` to be an array");
106 }
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000107 BlazeLabel target{std::string(target_and_headers["t"])};
108 if (target.value().empty()) {
109 return absl::InvalidArgumentError(
110 "Expected `t` fields of `--targets_and_headers` to be a non-empty "
111 "string");
112 }
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000113 for (const auto& header : target_and_headers["h"]) {
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000114 if (!header.is_string()) {
115 return absl::InvalidArgumentError(
116 "Expected `h` fields of `--targets_and_headers` to be an array of "
117 "strings");
118 }
119 std::string header_str(header);
120 if (header_str.empty()) {
121 return absl::InvalidArgumentError(
122 "Expected `h` fields of `--targets_and_headers` to be an array of "
123 "non-empty strings");
124 }
125 const auto [it, inserted] = cmdline.headers_to_targets_.insert(
126 std::make_pair(HeaderName(header_str), target));
127 if (!inserted) {
128 return absl::InvalidArgumentError(absl::Substitute(
129 "The `--targets_and_headers` cmdline argument assigns "
130 "`$0` header to two conflicting targets: `$1` vs `$2`",
131 header_str, target.value(), it->second.value()));
132 }
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000133 }
134 }
135
136 ASSIGN_OR_RETURN(cmdline.current_target_,
137 cmdline.FindHeader(cmdline.public_headers_[0]));
138 for (const HeaderName& public_header : cmdline.public_headers_) {
139 ASSIGN_OR_RETURN(BlazeLabel header_target,
140 cmdline.FindHeader(public_header));
141
142 if (cmdline.current_target_ != header_target) {
143 return absl::InvalidArgumentError(absl::Substitute(
144 "Expected all public headers to belong to the current target '$0', "
145 "but header '$1' belongs to '$2'",
146 cmdline.current_target_.value(), public_header.IncludePath(),
147 header_target.value()));
148 }
149 }
150
151 return cmdline;
152}
153
154absl::StatusOr<BlazeLabel> Cmdline::FindHeader(const HeaderName& header) const {
155 auto it = headers_to_targets_.find(header);
156 if (it == headers_to_targets_.end()) {
157 return absl::InvalidArgumentError(absl::Substitute(
158 "Couldn't find header '$0' in the `headers_to_target` map "
159 "derived from the --targets_and_headers cmdline argument",
160 header.IncludePath()));
161 }
162 return it->second;
163}
164
165Cmdline::Cmdline() = default;
166
167} // namespace rs_bindings_from_cc