feat(autogen): basic autogen starter
This commit is contained in:
parent
28ddfed8b3
commit
99a8cbe957
9 changed files with 260 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
/bin/
|
||||
/lib/
|
||||
/build/
|
||||
/autogen/build
|
||||
|
||||
# Qt
|
||||
/*.pro.user*
|
||||
|
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
@ -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}",
|
||||
}
|
||||
]
|
||||
}
|
|
@ -21,4 +21,3 @@ For build instructions, see [BUILD.md](./BUILD.md)
|
|||
### Assets
|
||||
|
||||
* Skybox by Jauhn Dabz
|
||||
* Icons by Mark James
|
12
autogen/CMakeLists.txt
Normal file
12
autogen/CMakeLists.txt
Normal 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
85
autogen/src/cache.cpp
Normal 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
10
autogen/src/cache.h
Normal 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
115
autogen/src/main.cpp
Normal 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
17
autogen/src/util.cpp
Normal 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
11
autogen/src/util.h
Normal 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);
|
Loading…
Add table
Reference in a new issue