Commit 51b3bf20 authored by Pádraig Ó Conbhuí's avatar Pádraig Ó Conbhuí
Browse files

Move classes from megadep.main.cpp into libmegadep

parent d94d9e6f
......@@ -4,6 +4,7 @@ Checks: '*,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-fuchsia-default-arguments,
-fuchsia-overloaded-operator,
-fuchsia-trailing-return,
-google-runtime-references,
-hicpp-no-array-decay'
......
......@@ -25,6 +25,7 @@ set_target_properties(
)
target_compile_features(libmegadep PUBLIC cxx_std_17)
target_include_directories(libmegadep PUBLIC src)
target_link_system_libraries(libmegadep PUBLIC CLI11::CLI11)
add_executable(
......@@ -32,7 +33,6 @@ add_executable(
src/megadep.main.cpp
)
target_link_libraries(megadep PRIVATE libmegadep)
target_link_system_libraries(megadep PRIVATE CLI11::CLI11)
option(MEGADEP_ENABLE_TESTING "Enable testing for Megadep" ON)
......
#include <megadep/filesystem.hpp>
#include <megadep/cli.hpp>
#include <megadep/megadep.hpp>
#include <megadep/serial_dependency_calculation.hpp>
#include <CLI/CLI.hpp>
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
// List of file suffixes the program will consider
constexpr std::array<std::string_view, 8> suffixes = {
".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx",
};
// Test if a given file name has a suffix in suffixes.
bool contains_suffix(std::string_view file_name) {
for (const auto &suffix : suffixes) {
if (file_name.compare(file_name.size() - suffix.size(), suffix.size(),
suffix) == 0) {
return true;
}
}
return false;
}
template <typename T> class bidirectional_index_map {
std::vector<T> m_values;
public:
bidirectional_index_map(std::vector<T> value_list)
: m_values{std::move(value_list)} {
// Sort the files so we can refer to them by index, and look them up
// quickly by index.
// Also, erase any duplicates using a unique / erase.
std::sort(m_values.begin(), m_values.end());
m_values.erase(std::unique(m_values.begin(), m_values.end()),
m_values.end());
}
const std::string &operator[](size_t index) const {
assert(index < m_values.size());
return m_values[index];
}
const std::optional<uint64_t> operator[](const T &value) const {
auto found_ptr = std::lower_bound(m_values.begin(), m_values.end(), value);
if (*found_ptr == value) {
return std::distance(m_values.begin(), found_ptr);
} else {
return std::nullopt;
}
}
size_t size() const { return m_values.size(); }
};
class serial_megadep_policy {
public:
template <typename ScanDirectories>
static bidirectional_index_map<std::string>
build_file_map(const ScanDirectories &scan_directories) {
std::vector<std::string> file_names_list;
for (const auto &directory : scan_directories) {
auto directory_file_names =
megadep::list_files(std::string{directory.data(), directory.size()});
for (const auto &file_name : directory_file_names) {
if (contains_suffix(file_name)) {
file_names_list.emplace_back(file_name);
}
}
}
return bidirectional_index_map<std::string>(std::move(file_names_list));
}
template <typename FileNamesMap, typename IncludeDirectories>
auto
build_direct_includes_map(const FileNamesMap &file_names_map,
const IncludeDirectories &include_directories) {
return build_direct_includes_map(0, file_names_map.size(), file_names_map,
include_directories);
}
template <typename FileNamesMap, typename IncludeDirectories>
std::vector<std::vector<uint64_t>>
build_direct_includes_map(size_t file_index_begin, size_t file_index_end,
const FileNamesMap &file_names_map,
const IncludeDirectories &include_directories) {
// Map from a file index to the file indices it #includes.
std::vector<std::vector<uint64_t>> file_to_includes(file_names_map.size());
for (size_t file_index = file_index_begin; file_index < file_index_end;
file_index++) {
const auto &file_name = file_names_map[file_index];
const auto include_strings = megadep::read_includes(file_name);
for (const auto &include : include_strings) {
auto resolved_include =
megadep::resolve_include(include, file_name, include_directories);
// If the resolved include is in the form <filename>, it's not in
// the includes, so it's an external depenency.
if (resolved_include.front() == '<' && resolved_include.back() == '>') {
continue;
}
auto included_file_index = file_names_map[resolved_include];
if (included_file_index) {
// If we found the exact value in file_names, add it to the includes
file_to_includes[file_index].emplace_back(*included_file_index);
} else {
// If we haven't found the exact value in file_names, it's in our
// includes, but it's not in the tracked list.
std::cerr << "Error: unresolved include!\n";
std::cerr << " Include: " << include << '\n';
std::cerr << " Attempt: " << resolved_include << '\n';
std::cerr << " Source : " << file_name << '\n';
std::exit(EXIT_FAILURE);
}
}
}
return file_to_includes;
}
template <typename FileToIncludes>
static std::vector<std::vector<uint64_t>>
invert_direct_includes_map(const FileToIncludes &file_to_includes) {
// Invert the map
std::vector<std::vector<uint64_t>> file_to_includers(
file_to_includes.size());
for (size_t includer = 0; includer < file_to_includes.size(); includer++) {
for (const auto &included : file_to_includes[includer]) {
file_to_includers[included].push_back(includer);
}
}
return file_to_includers;
}
template <typename FileToIncluders>
std::vector<std::vector<uint64_t>>
build_indirect_includes_map(const FileToIncluders &file_to_includers) {
std::vector<std::vector<uint64_t>> file_to_indirect_includes(
file_to_includers.size());
for (size_t file_index = 0; file_index < file_to_includers.size();
file_index++) {
std::vector<bool> processed(file_to_includers.size(), false);
// For `includer`, add `file_index` as an indirect dependency of
// every file it includes. The recurse on each of those files.
const auto recursive_add_to_includers =
[&](auto recursive_add_to_includers, auto includer) -> void {
for (const auto &sub_includer : file_to_includers[includer]) {
if (processed[sub_includer]) {
continue;
}
processed[sub_includer] = true;
file_to_indirect_includes[sub_includer].push_back(file_index);
recursive_add_to_includers(recursive_add_to_includers, sub_includer);
}
};
recursive_add_to_includers(recursive_add_to_includers, file_index);
}
return file_to_indirect_includes;
}
template <typename FileToIncludes, typename FileNamesMap>
static void write_includes(const FileToIncludes &file_to_includes,
const FileNamesMap &file_names,
std::string output_file_name) {
std::ofstream direct_includes_file(output_file_name, std::ofstream::out);
for (size_t file_index = 0; file_index < file_to_includes.size();
file_index++) {
if (file_index != 0) {
direct_includes_file << '\n';
}
direct_includes_file << file_names[file_index] << ":\n";
for (const auto &include : file_to_includes[file_index]) {
direct_includes_file << " - " << file_names[include] << '\n';
}
}
direct_includes_file.close();
}
template<typename String, typename... Strings>
static void print(String&& string, Strings&&... strings) {
std::cout << string;
print(std::forward<Strings>(strings)...);
}
static void print() {}
};
template <typename ScanDirectories, typename IncludeDirectories,
typename MegadepPolicy>
int run_megadep(const ScanDirectories &scan_directories,
const IncludeDirectories &include_directories,
MegadepPolicy &&megadep_policy) {
megadep_policy.print("Scanning:\n");
for (const auto &directory : scan_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
megadep_policy.print("Including:\n");
for (const auto &directory : include_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
auto file_names = megadep_policy.build_file_map(scan_directories);
//
// Forward direct include map
//
auto file_to_includes =
megadep_policy.build_direct_includes_map(file_names, include_directories);
megadep_policy.write_includes(file_to_includes, file_names,
"direct-includes.yml");
//
// Inverse direct include map
//
auto file_to_includers =
megadep_policy.invert_direct_includes_map(file_to_includes);
//
// Forward recursive include map
//
auto file_to_indirect_includes =
megadep_policy.build_indirect_includes_map(file_to_includers);
megadep_policy.write_includes(file_to_indirect_includes, file_names,
"indirect-includes.yml");
return EXIT_SUCCESS;
}
template <typename ScanDirectories, typename IncludeDirectories>
int run_megadep(const ScanDirectories &scan_directories,
const IncludeDirectories &include_directories) {
return run_megadep(scan_directories, include_directories,
serial_megadep_policy{});
}
struct cli_options {
std::vector<std::string> scan_directories;
std::vector<std::string> include_directories;
};
class cli_parser {
CLI::App app;
std::vector<std::string> m_scan_directories;
std::vector<std::string> m_include_directories;
std::vector<std::string> m_system_include_directories;
public:
cli_parser() : app{"megadep: distributed dependency scanner"} {
app.add_option("scan_directory", m_scan_directories, "Scan directory");
app.add_option("--include,-I", m_include_directories, "Include directory");
app.add_option("--isystem", m_system_include_directories,
"System include directory");
}
std::variant<cli_options, int> parse(int argc, char **argv) {
auto fixed_args = fixup_args(app, argc, argv);
auto fixed_argv = arg_ptrs(fixed_args);
try {
app.parse(argc, fixed_argv.data());
} catch (const CLI::ParseError &e) {
return app.exit(e);
}
// Combine include_directories and system_include_directories
std::vector<std::string> combined_includes;
combined_includes.reserve(m_include_directories.size() +
m_system_include_directories.size());
std::copy(m_include_directories.begin(), m_include_directories.end(),
std::back_inserter(combined_includes));
std::copy(m_system_include_directories.begin(),
m_system_include_directories.end(),
std::back_inserter(combined_includes));
return cli_options{m_scan_directories, combined_includes};
}
// Fix any -include or -isystem options to --include and --isystem
static std::vector<std::string> fixup_args(const CLI::App &app, int argc,
char **argv) {
std::vector<std::string> args{argv, std::next(argv, argc)};
const auto options = app.get_options();
for (auto &arg : args) {
// We have at least -- or -x
if (arg.size() < 2) {
continue;
}
// We're starting with a '-'
if (arg[0] != '-') {
continue;
}
// We've got -X and not --X
if (arg[1] == '-') {
continue;
}
const std::string_view arg_view = arg;
for (const auto &option : options) {
const auto lnames = option->get_lnames();
for (const auto &lname : lnames) {
// if we have -lname, turn it into --lname
if (arg_view.substr(1, lname.size()) == lname) {
arg.insert(0, 1, '-');
}
}
}
}
return args;
}
static std::vector<const char *>
arg_ptrs(const std::vector<std::string> &argv) {
std::vector<const char *> result;
result.reserve(argv.size());
for (const auto &arg : argv) {
result.push_back(arg.c_str());
}
return result;
}
};
// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
......@@ -368,14 +11,16 @@ template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
int main(int argc, char *argv[]) {
try {
auto parser = cli_parser();
auto parser = megadep::cli_parser();
auto parse_result = parser.parse(argc, argv);
return std::visit(overloaded{[](int exit_value) { return exit_value; },
[](const cli_options &options) {
return run_megadep(
[](const megadep::cli_options &options) {
megadep::megadep(
megadep::serial_dependency_calculation{},
options.scan_directories,
options.include_directories);
return EXIT_SUCCESS;
}},
parse_result);
} catch (const std::bad_variant_access &) {
......
#ifndef MEGADEP_BIDIRECTIONAL_INDEX_MAP
#define MEGADEP_BIDIRECTIONAL_INDEX_MAP
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace megadep {
template <typename T> class bidirectional_index_map {
std::vector<T> m_values;
public:
explicit bidirectional_index_map(std::vector<T> value_list)
: m_values{std::move(value_list)} {
// Sort the files so we can refer to them by index, and look them up
// quickly by index.
// Also, erase any duplicates using a unique / erase.
std::sort(m_values.begin(), m_values.end());
m_values.erase(std::unique(m_values.begin(), m_values.end()),
m_values.end());
}
const std::string &operator[](size_t index) const {
assert(index < m_values.size());
return m_values[index];
}
std::optional<uint64_t> operator[](const T &value) const {
auto found_ptr = std::lower_bound(m_values.begin(), m_values.end(), value);
if (*found_ptr != value) {
return std::nullopt;
}
return std::distance(m_values.begin(), found_ptr);
}
[[nodiscard]] size_t size() const { return m_values.size(); }
};
} // namespace megadep
#endif // MEGADEP_BIDIRECTIONAL_INDEX_MAP
#ifndef MEGADEP_CLI_HPP
#define MEGADEP_CLI_HPP
#include <CLI/CLI.hpp>
#include <algorithm>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
namespace megadep {
struct cli_options {
std::vector<std::string> scan_directories;
std::vector<std::string> include_directories;
};
class cli_parser {
CLI::App app;
std::vector<std::string> m_scan_directories {};
std::vector<std::string> m_include_directories {};
std::vector<std::string> m_system_include_directories {};
public:
cli_parser() : app{"megadep: distributed dependency scanner"} {
app.add_option("scan_directory", m_scan_directories, "Scan directory");
app.add_option("--include,-I", m_include_directories, "Include directory");
app.add_option("--isystem", m_system_include_directories,
"System include directory");
}
std::variant<cli_options, int> parse(int argc, char **argv) {
auto fixed_args = fixup_args(app, argc, argv);
auto fixed_argv = arg_ptrs(fixed_args);
try {
app.parse(argc, fixed_argv.data());
} catch (const CLI::ParseError &e) {
return app.exit(e);
}
// Combine include_directories and system_include_directories
std::vector<std::string> combined_includes;
combined_includes.reserve(m_include_directories.size() +
m_system_include_directories.size());
std::copy(m_include_directories.begin(), m_include_directories.end(),
std::back_inserter(combined_includes));
std::copy(m_system_include_directories.begin(),
m_system_include_directories.end(),
std::back_inserter(combined_includes));
return cli_options{m_scan_directories, combined_includes};
}
private:
// Fix any -include or -isystem options to --include and --isystem
static std::vector<std::string> fixup_args(const CLI::App &app, int argc,
char **argv) {
std::vector<std::string> args{argv, std::next(argv, argc)};
const auto options = app.get_options();
for (auto &arg : args) {
// We have at least -- or -x
if (arg.size() < 2) {
continue;
}
// We're starting with a '-'
if (arg[0] != '-') {
continue;
}
// We've got -X and not --X
if (arg[1] == '-') {
continue;
}
const std::string_view arg_view = arg;
for (const auto &option : options) {
const auto lnames = option->get_lnames();
for (const auto &lname : lnames) {
// if we have -lname, turn it into --lname
if (arg_view.substr(1, lname.size()) == lname) {
arg.insert(0, 1, '-');
}
}
}
}
return args;
}
static std::vector<const char *>
arg_ptrs(const std::vector<std::string> &argv) {
std::vector<const char *> result;
result.reserve(argv.size());
for (const auto &arg : argv) {
result.push_back(arg.c_str());
}
return result;
}
};
} // namespace megadep
#endif // MEGADEP_CLI_HPP
#ifndef MEGADEP_MEGADEP_HPP
#define MEGADEP_MEGADEP_HPP
#include <megadep/serial_dependency_calculation.hpp>
namespace megadep {
template <typename MegadepPolicy, typename ScanDirectories,
typename IncludeDirectories>
void megadep(MegadepPolicy &&megadep_policy,
const ScanDirectories &scan_directories,
const IncludeDirectories &include_directories) {
megadep_policy.print("Scanning:\n");
for (const auto &directory : scan_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
megadep_policy.print("Including:\n");
for (const auto &directory : include_directories) {
megadep_policy.print(" - ", directory, '\n');
}
megadep_policy.print('\n');
auto file_names = megadep_policy.build_file_map(scan_directories);
//
// Forward direct include map
//
auto file_to_includes =
megadep_policy.build_direct_includes_map(file_names, include_directories);
megadep_policy.write_includes(file_to_includes, file_names,
"direct-includes.yml");
//
// Inverse direct include map