feat(autogen): basic autogen starter

This commit is contained in:
maelstrom 2025-04-25 23:35:10 +02:00
parent 28ddfed8b3
commit 99a8cbe957
9 changed files with 260 additions and 2 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
/bin/
/lib/
/build/
/autogen/build
# Qt
/*.pro.user*

8
.vscode/launch.json vendored
View file

@ -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}",
}
]
}

View file

@ -20,5 +20,4 @@ For build instructions, see [BUILD.md](./BUILD.md)
### Assets
* Skybox by Jauhn Dabz
* Icons by Mark James
* Skybox by Jauhn Dabz

12
autogen/CMakeLists.txt Normal file
View file

@ -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})

85
autogen/src/cache.cpp Normal file
View file

@ -0,0 +1,85 @@
#include "cache.h"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <map>
#include <string>
#include <vector>
namespace fs = std::filesystem;
extern std::map<std::string, std::vector<std::string>> SUBCLASSES;
extern std::map<std::string, std::string> SUPERCLASS;
std::map<std::string, uint64_t> 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<std::chrono::milliseconds>(rawtime);
uint64_t time = std::chrono::duration_cast<std::chrono::milliseconds>(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<std::chrono::milliseconds>(rawtime);
uint64_t time = std::chrono::duration_cast<std::chrono::milliseconds>(rawtime_ms.time_since_epoch()).count();
LAST_MODIFIED_TIMES[path] = time;
}

10
autogen/src/cache.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include <filesystem>
#include <string>
void loadCaches(std::string outDir);
void flushCaches(std::string outDir);
bool hasFileBeenUpdated(std::string path);
void markFileCached(std::string path);

115
autogen/src/main.cpp Normal file
View file

@ -0,0 +1,115 @@
#include <clang-c/CXDiagnostic.h>
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/CXString.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <filesystem>
#include "cache.h"
#include "util.h"
namespace fs = std::filesystem;
bool analyzeFirstPass(std::string path, std::string srcRoot);
std::map<std::string, std::vector<std::string>> SUBCLASSES;
std::map<std::string, std::string> SUPERCLASS;
int main(int argc, char** argv) {
if (argc < 4) {
fprintf(stderr, "Usage: autogen <src-root> <parse-dir> <out-dir>\n");
return 1;
}
loadCaches(argv[3]);
std::vector<std::string> 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<std::string> subclasses = SUBCLASSES[baseClass];
subclasses.push_back(className);
SUBCLASSES[baseClass] = subclasses;
}
return CXChildVisit_Continue;
});
return true;
}

17
autogen/src/util.cpp Normal file
View file

@ -0,0 +1,17 @@
#include "util.h"
#include <clang-c/CXString.h>
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;
}

11
autogen/src/util.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <clang-c/Index.h>
#include <functional>
#include <string>
typedef std::function<CXChildVisitResult(CXCursor cursor, CXCursor parent)> X_CXCursorVisitor;
unsigned x_clang_visitChildren(CXCursor parent, X_CXCursorVisitor visitor);
std::string x_clang_toString(CXString string);