blob: 83ba925eaf0f41ff24e913f44b1d2dfdb78d84fd [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"
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +000014#include "third_party/absl/strings/str_cat.h"
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000015#include "third_party/absl/strings/substitute.h"
Lukasz Anforowiczd41a4e92022-03-17 17:08:57 +000016#include "rs_bindings_from_cc/util/status_macros.h"
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +000017#include "third_party/llvm/llvm-project/llvm/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.");
29ABSL_FLAG(std::vector<std::string>, public_headers, std::vector<std::string>(),
30 "public headers of the cc_library this tool should generate bindings "
31 "for, in a format suitable for usage in google3-relative quote "
32 "include (#include \"\").");
33ABSL_FLAG(std::string, targets_and_headers, std::string(),
34 "Information about which headers belong to which targets, encoded as "
35 "a JSON array. For example: "
36 "[\n"
37 " {\n"
38 " \"t\": \"//foo/bar:baz\",\n"
39 " \"h\": [\"foo/bar/header1.h\", \"foo/bar/header2.h\"]\n"
40 " },\n"
41 "...\n"
42 "]");
43
44namespace rs_bindings_from_cc {
45
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +000046namespace {
47
48struct TargetAndHeaders {
49 std::string target;
50 std::vector<std::string> headers;
51};
52
53bool fromJSON(const llvm::json::Value& json, TargetAndHeaders& out,
54 llvm::json::Path path) {
55 llvm::json::ObjectMapper mapper(json, path);
56 return mapper && mapper.map("t", out.target) && mapper.map("h", out.headers);
57}
58
59} // namespace
60
Lukasz Anforowicz016eb362022-03-02 18:37:03 +000061absl::StatusOr<Cmdline> Cmdline::Create() {
62 return CreateFromArgs(
63 absl::GetFlag(FLAGS_cc_out), absl::GetFlag(FLAGS_rs_out),
64 absl::GetFlag(FLAGS_ir_out), absl::GetFlag(FLAGS_do_nothing),
65 absl::GetFlag(FLAGS_public_headers),
66 absl::GetFlag(FLAGS_targets_and_headers));
67}
68
69absl::StatusOr<Cmdline> Cmdline::CreateFromArgs(
70 std::string cc_out, std::string rs_out, std::string ir_out, bool do_nothing,
71 std::vector<std::string> public_headers,
72 std::string targets_and_headers_str) {
73 Cmdline cmdline;
74
75 if (rs_out.empty()) {
76 return absl::InvalidArgumentError("please specify --rs_out");
77 }
78 cmdline.rs_out_ = std::move(rs_out);
79
80 if (cc_out.empty()) {
81 return absl::InvalidArgumentError("please specify --cc_out");
82 }
83 cmdline.cc_out_ = std::move(cc_out);
84
85 cmdline.ir_out_ = std::move(ir_out);
86 cmdline.do_nothing_ = do_nothing;
87
88 if (public_headers.empty()) {
89 return absl::InvalidArgumentError("please specify --public_headers");
90 }
91 std::transform(public_headers.begin(), public_headers.end(),
92 std::back_inserter(cmdline.public_headers_),
93 [](const std::string& s) { return HeaderName(s); });
94
95 if (targets_and_headers_str.empty()) {
96 return absl::InvalidArgumentError("please specify --targets_and_headers");
97 }
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +000098 auto targets_and_headers = llvm::json::parse<std::vector<TargetAndHeaders>>(
99 std::move(targets_and_headers_str));
100 if (auto err = targets_and_headers.takeError()) {
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000101 return absl::InvalidArgumentError(
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000102 absl::StrCat("Malformed `--targets_and_headers` argument: ",
103 toString(std::move(err))));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000104 }
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000105 for (const TargetAndHeaders& it : *targets_and_headers) {
106 const std::string& target = it.target;
107 if (target.empty()) {
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000108 return absl::InvalidArgumentError(
109 "Expected `t` fields of `--targets_and_headers` to be a non-empty "
110 "string");
111 }
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000112 for (const std::string& header : it.headers) {
113 if (header.empty()) {
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000114 return absl::InvalidArgumentError(
115 "Expected `h` fields of `--targets_and_headers` to be an array of "
116 "non-empty strings");
117 }
118 const auto [it, inserted] = cmdline.headers_to_targets_.insert(
Googler6c3de122022-03-28 11:40:41 +0000119 std::make_pair(HeaderName(header), BazelLabel(target)));
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000120 if (!inserted) {
121 return absl::InvalidArgumentError(absl::Substitute(
122 "The `--targets_and_headers` cmdline argument assigns "
123 "`$0` header to two conflicting targets: `$1` vs `$2`",
Lukasz Anforowicz3b4be122022-03-16 00:09:35 +0000124 header, target, it->second.value()));
Lukasz Anforowicz45f2d262022-03-02 18:37:27 +0000125 }
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000126 }
127 }
128
Lukasz Anforowiczd41a4e92022-03-17 17:08:57 +0000129 CRUBIT_ASSIGN_OR_RETURN(cmdline.current_target_,
130 cmdline.FindHeader(cmdline.public_headers_[0]));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000131 for (const HeaderName& public_header : cmdline.public_headers_) {
Googler6c3de122022-03-28 11:40:41 +0000132 CRUBIT_ASSIGN_OR_RETURN(BazelLabel header_target,
Lukasz Anforowiczd41a4e92022-03-17 17:08:57 +0000133 cmdline.FindHeader(public_header));
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000134
135 if (cmdline.current_target_ != header_target) {
136 return absl::InvalidArgumentError(absl::Substitute(
137 "Expected all public headers to belong to the current target '$0', "
138 "but header '$1' belongs to '$2'",
139 cmdline.current_target_.value(), public_header.IncludePath(),
140 header_target.value()));
141 }
142 }
143
144 return cmdline;
145}
146
Googler6c3de122022-03-28 11:40:41 +0000147absl::StatusOr<BazelLabel> Cmdline::FindHeader(const HeaderName& header) const {
Lukasz Anforowicz016eb362022-03-02 18:37:03 +0000148 auto it = headers_to_targets_.find(header);
149 if (it == headers_to_targets_.end()) {
150 return absl::InvalidArgumentError(absl::Substitute(
151 "Couldn't find header '$0' in the `headers_to_target` map "
152 "derived from the --targets_and_headers cmdline argument",
153 header.IncludePath()));
154 }
155 return it->second;
156}
157
158Cmdline::Cmdline() = default;
159
160} // namespace rs_bindings_from_cc