Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/mcg-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
stat /tmp/metacg/bin/cgquery
stat /tmp/metacg/bin/cgformat
stat /tmp/metacg/bin/cgconvert
stat /tmp/metacg/bin/cgtodot
- name: Check for canonical test graph format
uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a
with:
Expand Down Expand Up @@ -196,4 +197,10 @@ jobs:
with:
image: metacg-devel:latest
run: /opt/metacg/build/tools/cgdiff/test/unit/cgdifftests

- name: Run cgtodot tests
uses: maus007/docker-run-action-fork@207a4e2a8ebf7e4b985656ba990b1e53715dce2a
with:
image: metacg-devel:latest
run: |
cd /opt/metacg/build/tools/cgtodot/
ctest . --verbose
1 change: 1 addition & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_subdirectory(cgconvert)
add_subdirectory(cgformat)
add_subdirectory(cgdiff)
add_subdirectory(cgquery)
add_subdirectory(cgtodot)
if(BUILD_CAGE)
add_subdirectory(cage)
endif()
99 changes: 99 additions & 0 deletions tools/cgtodot/CGToDot.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* File: CGToDot.cpp
* License: Part of the MetaCG project. Licensed under BSD 3 clause license. See LICENSE.txt file at
* https://github.com/tudasc/metacg/LICENSE.txt
*/

#include "Callgraph.h"
#include "DotIO.h"
#include "LoggerUtil.h"
#include "io/MCGReader.h"

#include <cxxopts.hpp>
#include <fstream>

static auto console = metacg::MCGLogger::instance().getConsole();
static auto errConsole = metacg::MCGLogger::instance().getErrConsole();

template <>
struct fmt::formatter<std::filesystem::path> {
constexpr auto parse(fmt::format_parse_context& ctx) { return ctx.begin(); }

template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}", p.string());
}
};

int main(int argc, char* argv[]) {
cxxopts::Options options("CGToDot",
"Reads a call graph from the specified file and generates a DOT file for visualization.");

// clang-format off
options.add_options()
("h,help", "Show this help message")
("o,output", "Specify output DOT file name", cxxopts::value<std::string>())
("input", "Input call graph file", cxxopts::value<std::string>())
;
// clang-format on

options.parse_positional({"input"});

cxxopts::ParseResult result = options.parse(argc, argv);

if (result.count("help")) {
std::cout << options.help() << std::endl;
return EXIT_SUCCESS;
}

if (!result.count("input")) {
errConsole->error("No input call graph file specified.");
std::cout << options.help() << std::endl;
return EXIT_FAILURE;
}

std::filesystem::path inputFile = result["input"].as<std::string>();
if (!std::filesystem::exists(inputFile)) {
errConsole->error("Specified input file does not exist: {}", inputFile);
return EXIT_FAILURE;
}

std::filesystem::path outputFile;
if (result.count("output")) {
outputFile = result["output"].as<std::string>();
} else {
outputFile = inputFile.stem().string() + ".dot";
}
if (std::filesystem::exists(outputFile)) {
errConsole->warn("Output file already exists and will be overwritten: {}", outputFile);
}

metacg::io::FileSource fs(inputFile);

auto mcgReader = metacg::io::createReader(fs);
if (!mcgReader) {
errConsole->error("Failed to create MCG reader for file: {}", inputFile);
return EXIT_FAILURE;
}

auto cg = mcgReader->read();
if (!cg) {
errConsole->error("Failed to read call graph from file.");
return EXIT_FAILURE;
}

metacg::io::dot::DotGenerator dotGen(cg.get());
dotGen.generate();

std::ofstream outFile(outputFile);
if (!outFile.is_open()) {
errConsole->error("Could not open output file for writing: {}", outputFile);
return EXIT_FAILURE;
}

outFile << dotGen.getDotString();
outFile.close();

console->info("DOT file generated successfully: {}", outputFile);
return EXIT_SUCCESS;
}
31 changes: 31 additions & 0 deletions tools/cgtodot/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
set(PROJECT_NAME CGToDot)
set(TARGETS_EXPORT_NAME ${PROJECT_NAME}-target)
set(PROJECT_BINARY_NAME cgtodot)

add_executable(${PROJECT_BINARY_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/CGToDot.cpp)

add_metacg(${PROJECT_BINARY_NAME})
add_cxxopts(${PROJECT_BINARY_NAME})
add_spdlog_libraries(${PROJECT_BINARY_NAME})

install(
TARGETS ${PROJECT_BINARY_NAME}
EXPORT ${TARGETS_EXPORT_NAME}
RUNTIME DESTINATION bin
)

configure_package_config_file(
${METACG_Directory}/cmake/Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION lib/cmake
)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake DESTINATION lib/cmake)

# tests

set(CGTODOT_BIN ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_BINARY_NAME})
set(CGTODOT_TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test/data)

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/test/test_runner.sh.in" "test_runner.sh")

add_test(NAME cgtodot_tests COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_runner.sh)
12 changes: 12 additions & 0 deletions tools/cgtodot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# CGToDot

CGToDot is a simple tool to generate a DOT file representation of a given call
graph.

## Usage

```
cgtodot [options] <input_file>
```

Use `cgtodot --help` to get a list of available options.
29 changes: 29 additions & 0 deletions tools/cgtodot/test/data/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"_CG": {
"meta": {},
"nodes": {
"0": {
"callees": {},
"functionName": "_QFPprint_stars",
"hasBody": true,
"meta": {},
"origin": "input.f90"
},
"1": {
"callees": { "0": {} },
"functionName": "_QQmain",
"hasBody": true,
"meta": {},
"origin": "input.f90"
}
}
},
"_MetaCG": {
"generator": {
"name": "MetaCG",
"sha": "68fb73aebcc0af419653b36a6b5e3e9668408d10",
"version": "0.9"
},
"version": "4.0"
}
}
6 changes: 6 additions & 0 deletions tools/cgtodot/test/data/output.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
digraph callgraph {
"_QFPprint_stars"
"_QQmain"

_QQmain -> _QFPprint_stars
}
78 changes: 78 additions & 0 deletions tools/cgtodot/test/test_runner.sh.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash

# Test runner for CGToDot.
#
# Usage:
# test_runner.sh

binary="@CGTODOT_BIN@"
data_dir="@CGTODOT_TEST_DATA_DIR@"
input_file="$data_dir/input.json"
expected_output_file="$data_dir/output.dot"

tests_failed=0
tests_passed=0

fail()
{
echo "Test failed: $1"
tests_failed=$((tests_failed + 1))
}

pass()
{
echo "Test passed: $1"
tests_passed=$((tests_passed + 1))
}

# Test: no input
if "$binary" >/dev/null 2>&1; then
fail "no input"
else
pass "no input"
fi

# Test: non-existent input file
if "$binary" "non_existent_file.json" >/dev/null 2>&1; then
fail "non-existent input file"
else
pass "non-existent input file"
fi

# Test: valid input file -> default output
default_output_file="input.dot"
if "$binary" "$input_file" >/dev/null 2>&1; then
if [ -f "$default_output_file" ] && diff -q "$default_output_file" "$expected_output_file"; then
pass "valid input file -> default output"
else
fail "valid input file -> default output"
fi
else
fail "valid input file -> default output"
fi

rm -f "$default_output_file"

# Test: option -o
custom_output_file_name="$(mktemp custom_output_file_name_XXXXXX.dot)"
if "$binary" -o "$custom_output_file_name" "$input_file" >/dev/null 2>&1; then
if diff -q "$custom_output_file_name" "$expected_output_file" >/dev/null; then
pass "option -o"
else
fail "option -o"
fi
else
fail "option -o"
fi

rm -f "$custom_output_file_name"

echo ""
echo "Tests passed: $tests_passed"
echo "Tests failed: $tests_failed"

if [ $tests_failed -ne 0 ]; then
exit 1
else
exit 0
fi