blob: 6db10f1c6ee1e9b5b894ccb2fb769dc40eeece78 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include "llcpp_codegen.h"
#include "rapidjson/document.h"
#include "rapidjson/error/en.h"
#include "rapidjson/istreamwrapper.h"
namespace fs = std::filesystem;
namespace {
[[noreturn]] void FatalError(const std::string& info) {
std::cerr << "Error: " << info << ", errno: " << strerror(errno) << std::endl;
exit(1);
}
rapidjson::Document ReadMetadata(const fs::path& path) {
rapidjson::Document metadata;
std::ifstream contents(path);
if (contents.fail()) {
FatalError("Failed to read GN metadata at " + std::string(path));
}
rapidjson::IStreamWrapper isw(contents);
rapidjson::ParseResult parse_result = metadata.ParseStream(isw);
if (!parse_result) {
FatalError("Failed to parse " + std::string(path) + ", " +
rapidjson::GetParseError_En(parse_result.Code()) + ", offset " +
std::to_string(parse_result.Offset()));
}
if (!metadata.IsArray()) {
FatalError("Metadata is not an array");
}
return metadata;
}
struct Target {
fs::path gen_dir;
std::string name;
std::vector<fs::path> fidl_sources;
std::vector<std::string> args;
fs::path json;
fs::path header;
fs::path source;
fs::path include_base;
};
std::vector<Target> AllTargets(const fs::path& zircon_build_root) {
std::vector<Target> targets_vector;
const auto metadata = ReadMetadata(zircon_build_root / "fidl_gen.json");
for (auto value_it = metadata.Begin();
value_it != metadata.End(); ++value_it) {
const auto& target = *value_it;
const rapidjson::Value& args = target["args"];
if (!args.IsArray()) {
FatalError("args in metadata JSON must be an array");
}
std::vector<std::string> args_vector;
for (auto arg_it = args.Begin(); arg_it != args.End(); ++arg_it) {
args_vector.emplace_back(arg_it->GetString());
}
const rapidjson::Value& fidl_sources = target["fidl_sources"];
if (!fidl_sources.IsArray()) {
FatalError("fidl_sources in metadata JSON must be an array");
}
std::vector<fs::path> fidl_sources_vector;
for (auto it = fidl_sources.Begin(); it != fidl_sources.End(); ++it) {
fidl_sources_vector.emplace_back(fs::path(it->GetString()));
}
targets_vector.push_back(Target {
.gen_dir = fs::path(target["target_gen_dir"].GetString()),
.name = target["name"].GetString(),
.args = std::move(args_vector),
.fidl_sources = std::move(fidl_sources_vector),
.json = fs::path(target["json"].GetString()),
.header = fs::path(target["header"].GetString()),
.source = fs::path(target["source"].GetString()),
.include_base = fs::path(target["include_base"].GetString()),
});
}
return targets_vector;
}
// Run a command with the specified command, working directory, and arguments.
void RunCommand(const std::string& cmd, const std::string& working_directory,
std::vector<std::string> args) {
pid_t pid = fork();
int status;
switch (pid) {
case -1:
FatalError("Failed to fork");
case 0: {
status = chdir(working_directory.c_str());
if (status != 0) {
FatalError("Failed to chdir to " + working_directory);
}
std::vector<char *> c_args;
c_args.push_back(const_cast<char *>(cmd.c_str()));
for (const auto& arg : args) {
c_args.push_back(const_cast<char *>(arg.c_str()));
}
c_args.push_back(nullptr);
execv(cmd.c_str(), &c_args[0]);
FatalError("when executing " + cmd + ", execv should not return");
}
default:
pid_t ret_pid = waitpid(pid, &status, 0);
if (pid != ret_pid) {
FatalError("when executing " + cmd +
", unexpected return value from waitpid: " +
std::to_string(ret_pid));
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
FatalError(cmd + " returned an error: " + std::to_string(status));
}
}
}
fs::path FindCommonPath(fs::path a, fs::path b) {
auto a_it = a.begin();
auto b_it = b.begin();
fs::path result;
while (a_it != a.end() && b_it != b.end()) {
auto& a_part = *a_it;
auto& b_part = *b_it;
if (a_part != b_part) {
break;
}
result /= a_part;
a_it++;
b_it++;
}
return result;
}
bool Diff(fs::path a, fs::path b) {
std::ifstream a_stream(a.c_str(), std::ios::binary | std::ios::ate);
std::ifstream b_stream(b.c_str(), std::ios::binary | std::ios::ate);
if (!a_stream) {
return false;
}
if (!b_stream) {
return false;
}
if (a_stream.tellg() != b_stream.tellg()) {
return false;
}
a_stream.seekg(0, std::ifstream::beg);
b_stream.seekg(0, std::ifstream::beg);
return std::equal(std::istreambuf_iterator<char>(a_stream.rdbuf()),
std::istreambuf_iterator<char>(),
std::istreambuf_iterator<char>(b_stream.rdbuf()));
}
} // namespace
bool DoValidate(std::filesystem::path zircon_build_root,
std::filesystem::path fidlgen_llcpp_path,
std::filesystem::path tmp_dir,
std::vector<fs::path>* out_dependencies) {
fs::remove_all(tmp_dir);
fs::create_directories(tmp_dir);
auto all_targets = AllTargets(zircon_build_root);
auto normalize = [&zircon_build_root](fs::path path) {
return fs::weakly_canonical(zircon_build_root / path);
};
for (const auto& target : all_targets) {
for (const auto& source : target.fidl_sources) {
out_dependencies->push_back(zircon_build_root / source);
}
fs::path json = normalize(target.json);
fs::path header = normalize(target.header);
fs::path source = normalize(target.source);
fs::path include_base = normalize(target.include_base);
fs::path common = FindCommonPath(header,
FindCommonPath(include_base, source));
// Generate in an alternative location
fs::path tmp = fs::absolute(tmp_dir) / target.name;
fs::path alt_header = tmp / fs::relative(header, common);
fs::path alt_source = tmp / fs::relative(source, common);
fs::path alt_include_base = tmp / fs::relative(include_base, common);
std::vector<std::string> args = {
"-json",
json,
"-include-base",
alt_include_base,
"-header",
alt_header,
"-source",
alt_source
};
RunCommand(fidlgen_llcpp_path, zircon_build_root, args);
if (!Diff(header, alt_header)) {
std::cerr << header << " is different from " << alt_header << std::endl;
return false;
}
if (!Diff(source, alt_source)) {
std::cerr << source << " is different from " << alt_source << std::endl;
return false;
}
}
return true;
}
void DoUpdate(fs::path zircon_build_root,
fs::path fidlgen_llcpp_path,
std::vector<fs::path>* out_dependencies) {
const auto all_targets = AllTargets(zircon_build_root);
for (const auto& target : all_targets) {
for (const auto& source : target.fidl_sources) {
out_dependencies->push_back(zircon_build_root / source);
}
std::cout << "Generating low-level C++ bindings for " << target.name
<< std::endl;
RunCommand(fidlgen_llcpp_path, zircon_build_root, target.args);
}
}