blob: e6257df7414e1df1b4411cf10466e8896328f2a9 [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
// Parses C++ headers and generates:
// * a Rust source file with bindings for the C++ API
// * a C++ source file with the implementation of the bindings
#include <string>
#include <utility>
#include <vector>
#include "base/init_google.h"
#include "base/logging.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "rs_bindings_from_cc/ir_from_cc.h"
#include "rs_bindings_from_cc/src_code_gen.h"
#include "third_party/absl/container/flat_hash_map.h"
#include "third_party/absl/flags/flag.h"
#include "third_party/absl/meta/type_traits.h"
#include "third_party/absl/status/status.h"
#include "third_party/absl/status/statusor.h"
#include "third_party/absl/strings/string_view.h"
#include "third_party/json/src/json.hpp"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/FileSystem.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/raw_ostream.h"
#include "util/gtl/labs/string_type.h"
#include "util/task/status.h"
namespace {
absl::Status SetFileContents(const std::string& path,
absl::string_view contents) {
std::error_code error_code;
llvm::raw_fd_ostream stream(path, error_code);
if (error_code) {
return absl::Status(absl::StatusCode::kInternal, error_code.message());
}
stream << contents;
stream.close();
if (stream.has_error()) {
return absl::Status(absl::StatusCode::kInternal, stream.error().message());
}
return absl::OkStatus();
}
} // namespace
ABSL_FLAG(bool, do_nothing, false,
"if set to true the tool will produce empty files "
"(useful for testing Blaze 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::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"
"]");
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, true);
auto rs_out = absl::GetFlag(FLAGS_rs_out);
QCHECK(!rs_out.empty()) << "please specify --rs_out";
auto cc_out = absl::GetFlag(FLAGS_cc_out);
QCHECK(!cc_out.empty()) << "please specify --cc_out";
if (absl::GetFlag(FLAGS_do_nothing)) {
CHECK_OK(SetFileContents(
rs_out,
"// intentionally left empty because --do_nothing was passed."));
CHECK_OK(SetFileContents(
cc_out,
"// intentionally left empty because --do_nothing was passed."));
return 0;
}
auto public_headers = absl::GetFlag(FLAGS_public_headers);
QCHECK(!public_headers.empty())
<< "please specify at least one header in --public_headers";
auto targets_and_headers_json = absl::GetFlag(FLAGS_targets_and_headers);
QCHECK(!targets_and_headers_json.empty())
<< "please specify --targets_and_headers";
absl::flat_hash_map<const rs_bindings_from_cc::HeaderName,
const rs_bindings_from_cc::BlazeLabel>
headers_to_targets;
if (!targets_and_headers_json.empty()) {
nlohmann::json targets_and_headers =
nlohmann::json::parse(targets_and_headers_json);
QCHECK(targets_and_headers.is_array())
<< "Expected `--targets_and_headers` to be a Json array of objects";
for (const auto& target_and_headers : targets_and_headers) {
rs_bindings_from_cc::BlazeLabel target =
rs_bindings_from_cc::BlazeLabel{target_and_headers["t"]};
QCHECK(target_and_headers["h"].is_array())
<< "Expected `h` fields of `--targets_and_headers` "
"to be an array of strings";
for (std::string header : target_and_headers["h"]) {
headers_to_targets.insert(
std::pair<const rs_bindings_from_cc::HeaderName,
const rs_bindings_from_cc::BlazeLabel>(
rs_bindings_from_cc::HeaderName(header), target));
}
}
}
auto find_header = [&](const std::string& header) {
auto it = headers_to_targets.find(rs_bindings_from_cc::HeaderName(header));
QCHECK(it != headers_to_targets.end())
<< "Couldn't find header '" << header << "' in "
<< "the `headers_to_target` map derived from the "
<< "--targets_and_headers cmdline argument";
return it->second;
};
rs_bindings_from_cc::BlazeLabel current_target =
find_header(public_headers[0]);
for (const auto& public_header : public_headers) {
rs_bindings_from_cc::BlazeLabel header_target = find_header(public_header);
QCHECK(current_target == header_target)
<< "Expected all public headers to belong to the current target '"
<< current_target << "', but header '" << public_header
<< "' belongs to '" << header_target << "'";
}
auto ir_out = absl::GetFlag(FLAGS_ir_out); // Optional.
if (absl::StatusOr<rs_bindings_from_cc::IR> ir =
rs_bindings_from_cc::IrFromCc(
/* extra_source_code= */ "", current_target,
std::vector<rs_bindings_from_cc::HeaderName>(
public_headers.begin(), public_headers.end()),
/* virtual_headers_contents= */ {}, std::move(headers_to_targets),
std::vector<absl::string_view>(argv, argv + argc));
ir.ok()) {
if (!ir_out.empty()) {
CHECK_OK(SetFileContents(ir_out, ir->ToJson().dump(/*indent=*/2)));
}
rs_bindings_from_cc::Bindings bindings =
rs_bindings_from_cc::GenerateBindings(*ir);
CHECK_OK(SetFileContents(rs_out, bindings.rs_api));
CHECK_OK(SetFileContents(cc_out, bindings.rs_api_impl));
return 0;
}
llvm::sys::fs::remove(rs_out);
llvm::sys::fs::remove(cc_out);
if (!ir_out.empty()) {
llvm::sys::fs::remove(ir_out);
}
return 1;
}