From 99a8cbe9573ef08eb2826cd99e54119f5f4d622e Mon Sep 17 00:00:00 2001 From: maelstrom Date: Fri, 25 Apr 2025 23:35:10 +0200 Subject: [PATCH] feat(autogen): basic autogen starter --- .gitignore | 1 + .vscode/launch.json | 8 +++ README.md | 3 +- autogen/CMakeLists.txt | 12 +++++ autogen/src/cache.cpp | 85 ++++++++++++++++++++++++++++++ autogen/src/cache.h | 10 ++++ autogen/src/main.cpp | 115 +++++++++++++++++++++++++++++++++++++++++ autogen/src/util.cpp | 17 ++++++ autogen/src/util.h | 11 ++++ 9 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 autogen/CMakeLists.txt create mode 100644 autogen/src/cache.cpp create mode 100644 autogen/src/cache.h create mode 100644 autogen/src/main.cpp create mode 100644 autogen/src/util.cpp create mode 100644 autogen/src/util.h diff --git a/.gitignore b/.gitignore index 3e9b0c8..f65aff7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /bin/ /lib/ /build/ +/autogen/build # Qt /*.pro.user* diff --git a/.vscode/launch.json b/.vscode/launch.json index 0628366..010f65f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,14 @@ "program": "${workspaceFolder}/build/bin/editor", "args": [], "cwd": "${workspaceFolder}", + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug Autogen", + "program": "${workspaceFolder}/autogen/build/autogen", + "args": ["core/src", "core/src/objects", "autogen/build/generated"], + "cwd": "${workspaceFolder}", } ] } \ No newline at end of file diff --git a/README.md b/README.md index 48696df..a22ae5c 100644 --- a/README.md +++ b/README.md @@ -20,5 +20,4 @@ For build instructions, see [BUILD.md](./BUILD.md) ### Assets -* Skybox by Jauhn Dabz -* Icons by Mark James \ No newline at end of file +* Skybox by Jauhn Dabz \ No newline at end of file diff --git a/autogen/CMakeLists.txt b/autogen/CMakeLists.txt new file mode 100644 index 0000000..510981e --- /dev/null +++ b/autogen/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.30.0) +find_package(Clang REQUIRED) + +add_executable(autogen + src/main.cpp + src/util.cpp + src/cache.cpp +) + +set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen") +target_link_libraries(autogen -lclang) +target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIRS}) \ No newline at end of file diff --git a/autogen/src/cache.cpp b/autogen/src/cache.cpp new file mode 100644 index 0000000..c465ddb --- /dev/null +++ b/autogen/src/cache.cpp @@ -0,0 +1,85 @@ +#include "cache.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +extern std::map> SUBCLASSES; +extern std::map SUPERCLASS; + +std::map LAST_MODIFIED_TIMES; + +void loadModTimes(std::string path); +void writeModTimes(std::string path); + +void loadCaches(std::string outDir) { + fs::path cacheDir = fs::path(outDir) / ".cache"; + if (!fs::exists(cacheDir)) return; + + fs::path modtimesFile = cacheDir / "modified.txt"; + if (fs::exists(modtimesFile)) + loadModTimes(modtimesFile); +} + +void flushCaches(std::string outDir) { + fs::path cacheDir = fs::path(outDir) / ".cache"; + fs::create_directories(cacheDir); + + fs::path modtimesFile = cacheDir / "modified.txt"; + writeModTimes(modtimesFile); +} + +void loadModTimes(std::string path) { + std::ifstream stream(path); + + std::string line; + while (std::getline(stream, line)) { + int pos = line.find(":"); + std::string filename = line.substr(0, pos); + std::string timestr = line.substr(pos+1); + uint64_t time = std::stoull(timestr); + LAST_MODIFIED_TIMES[filename] = time; + } + + stream.close(); +} + +void writeModTimes(std::string path) { + std::ofstream stream(path); + + for (auto& [key, time] : LAST_MODIFIED_TIMES) { + stream << key.c_str() << ":" << time << "\n"; + } + + stream.close(); +} + +bool hasFileBeenUpdated(std::string path) { + path = fs::canonical(path); + if (LAST_MODIFIED_TIMES.count(path) == 0) return true; + + // https://stackoverflow.com/a/31258680/16255372 + auto rawtime = fs::last_write_time(path); + auto rawtime_ms = std::chrono::time_point_cast(rawtime); + uint64_t time = std::chrono::duration_cast(rawtime_ms.time_since_epoch()).count(); + + uint64_t cachedTime = LAST_MODIFIED_TIMES[path]; + return time > cachedTime; +} + +void markFileCached(std::string path) { + path = fs::canonical(path); + // https://stackoverflow.com/a/31258680/16255372 + auto rawtime = fs::last_write_time(path); + auto rawtime_ms = std::chrono::time_point_cast(rawtime); + uint64_t time = std::chrono::duration_cast(rawtime_ms.time_since_epoch()).count(); + + LAST_MODIFIED_TIMES[path] = time; +} \ No newline at end of file diff --git a/autogen/src/cache.h b/autogen/src/cache.h new file mode 100644 index 0000000..2e10202 --- /dev/null +++ b/autogen/src/cache.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +void loadCaches(std::string outDir); +void flushCaches(std::string outDir); + +bool hasFileBeenUpdated(std::string path); +void markFileCached(std::string path); \ No newline at end of file diff --git a/autogen/src/main.cpp b/autogen/src/main.cpp new file mode 100644 index 0000000..9fc03d8 --- /dev/null +++ b/autogen/src/main.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cache.h" +#include "util.h" + +namespace fs = std::filesystem; + +bool analyzeFirstPass(std::string path, std::string srcRoot); + +std::map> SUBCLASSES; +std::map SUPERCLASS; + +int main(int argc, char** argv) { + if (argc < 4) { + fprintf(stderr, "Usage: autogen \n"); + return 1; + } + + loadCaches(argv[3]); + + std::vector headerFiles; + + // Scrape directory for header files + for (const fs::directory_entry& file : fs::recursive_directory_iterator(argv[2])) { + if (!file.is_regular_file()) continue; // Not a file, skip + if (file.path().extension() != ".cpp") continue; // Not a header file, skip + if (!hasFileBeenUpdated(file.path())) { + fs::path relpath = fs::relative(file.path(), argv[1]); + printf("[AUTOGEN] Skipping file %s...\n", relpath.c_str()); + continue; + } + markFileCached(file.path()); + + headerFiles.push_back(file.path()); + } + + // First-pass: Analyze type hierarchy + for (std::string path : headerFiles) { + fs::path relpath = fs::relative(path, argv[1]); + printf("[AUTOGEN] [Stage 1] Analyzing file %s...\n", relpath.c_str()); + if (!analyzeFirstPass(path, argv[1])) + return 1; + } + + flushCaches(argv[3]); +} + +// https://clang.llvm.org/docs/LibClang.html +bool analyzeFirstPass(std::string path, std::string srcRoot) { + const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), 0 }; + // THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL + // https://stackoverflow.com/a/59206378/16255372 + CXIndex index = clang_createIndex(0, 0); + CXTranslationUnit unit = clang_parseTranslationUnit( + index, + path.c_str(), cargs, 4, + nullptr, 0, + CXTranslationUnit_None); + + if (!unit) { + fprintf(stderr, "Failed to parse file\n"); + return 1; + } + + // Print errors + int ndiags = clang_getNumDiagnostics(unit); + for (int i = 0; i < ndiags; i++) { + CXDiagnostic diag = clang_getDiagnostic(unit, i); + CXString str = clang_formatDiagnostic(diag, 0); + fprintf(stderr, "diag: %s\n", clang_getCString(str)); + + clang_disposeString(str); + clang_disposeDiagnostic(diag); + } + + CXCursor cursor = clang_getTranslationUnitCursor(unit); + + // Search for classes + x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) { + CXCursorKind kind = clang_getCursorKind(cur); + if (kind != CXCursor_ClassDecl) return CXChildVisit_Continue; + + std::string className = x_clang_toString(clang_getCursorDisplayName(cur)); + + std::string baseClass = ""; + x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) { + CXCursorKind kind = clang_getCursorKind(cur); + if (kind != CXCursor_CXXBaseSpecifier) return CXChildVisit_Continue; + + baseClass = x_clang_toString(clang_getCursorDisplayName(cur)); + return CXChildVisit_Break; + }); + + if (baseClass != "") { + SUPERCLASS[className] = baseClass; + + std::vector subclasses = SUBCLASSES[baseClass]; + subclasses.push_back(className); + SUBCLASSES[baseClass] = subclasses; + } + + return CXChildVisit_Continue; + }); + + return true; +} \ No newline at end of file diff --git a/autogen/src/util.cpp b/autogen/src/util.cpp new file mode 100644 index 0000000..300a1d6 --- /dev/null +++ b/autogen/src/util.cpp @@ -0,0 +1,17 @@ +#include "util.h" +#include + +static CXChildVisitResult _visitorFunc(CXCursor cursor, CXCursor parent, CXClientData client_data) { + X_CXCursorVisitor* func = (X_CXCursorVisitor*)client_data; + return (*func)(cursor, parent); +} + +unsigned x_clang_visitChildren(CXCursor parent, X_CXCursorVisitor visitor) { + return clang_visitChildren(parent, _visitorFunc, &visitor); +} + +std::string x_clang_toString(CXString string) { + std::string str(clang_getCString(string)); + clang_disposeString(string); + return str; +} \ No newline at end of file diff --git a/autogen/src/util.h b/autogen/src/util.h new file mode 100644 index 0000000..a868880 --- /dev/null +++ b/autogen/src/util.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +typedef std::function X_CXCursorVisitor; + +unsigned x_clang_visitChildren(CXCursor parent, X_CXCursorVisitor visitor); + +std::string x_clang_toString(CXString string); \ No newline at end of file