diff --git a/.clang-format b/.clang-format index dc33ed7..4c6611f 100644 --- a/.clang-format +++ b/.clang-format @@ -44,6 +44,7 @@ IncludeCategories: IncludeIsMainRegex: '([-_](test|unittest))?$' IndentAccessModifiers: false IndentCaseBlocks: true +IndentPPDirectives: AfterHash IndentWidth: 4 InsertNewlineAtEOF: true MacroBlockBegin: '' @@ -57,6 +58,7 @@ PenaltyBreakString: 100 PenaltyExcessCharacter: 1 PenaltyReturnTypeOnItsOwnLine: 1000 PointerAlignment: Left +PPIndentWidth: 1 SortIncludes: CaseInsensitive SpacesInAngles: Never SpacesInContainerLiterals: false diff --git a/.clangd b/.clangd index b0602b7..e0a1db3 100644 --- a/.clangd +++ b/.clangd @@ -3,6 +3,7 @@ CompileFlags: - --driver-mode=cl - -Wno-unknown-pragmas - -Wno-unused-command-line-argument + - /IJuliet Remove: - /Yu* - /Yc* diff --git a/.gitignore b/.gitignore index 90cf82b..0e5ae72 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # Binaries [Bb]in/ +[Bb]uild/ [Ll]ib/ .idea/ .vs/ diff --git a/Juliet/appbin/appbin_main.c b/Juliet/appbin/appbin_main.c new file mode 100644 index 0000000..1b05ebe --- /dev/null +++ b/Juliet/appbin/appbin_main.c @@ -0,0 +1,10 @@ +// ========================================================================= // +// Includes + +// Headers +#include "core/core_inc.h" + +// C +#include "core/core_inc.c" + +internal void entry_point() {} diff --git a/Juliet/core/core.c b/Juliet/core/core.c new file mode 100644 index 0000000..e69de29 diff --git a/Juliet/core/core.h b/Juliet/core/core.h new file mode 100644 index 0000000..6f2e7aa --- /dev/null +++ b/Juliet/core/core.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#define internal static +#define global static + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; + +typedef int8 bool8; +typedef int16 bool16; +typedef int32 bool32; +typedef int64 bool64; diff --git a/Juliet/core/core_defines.h b/Juliet/core/core_defines.h new file mode 100644 index 0000000..67559b7 --- /dev/null +++ b/Juliet/core/core_defines.h @@ -0,0 +1,91 @@ +#pragma once + +// ========================================================================== // +// Compiler defines | Clang + +#if defined(__clang__) + +# define COMPILER_CLANG 1 + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# else +# error Compiler / OS combo is not supported +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define ARCH_X64 1 +# else +# error Architecture not supported +# endif + +#elif defined(_MSC_VER) + +# define COMPILER_MSVC 1 + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# else +# error Compiler / OS combo is not supported +# endif + +# if defined(_M_AMD64) +# define ARCH_X64 1 +# else +# error Architecture not supported +# endif + +#else +# error Compiler not supported. +#endif + +// ========================================================================== // +// Language + +#if defined(__cplusplus) +# define LANG_CPP 1 +#else +# define LANG_C 1 +#endif + +// ========================================================================== // +// Build Options + +#if !defined(BUILD_DEBUG) +# define BUILD_DEBUG 1 +#endif + +// ========================================================================== // +// Zeroing unused Options + +#if !defined(ARCH_X64) +# define ARCH_X64 0 +#endif + +#if !defined(ARCH_X86) +# define ARCH_X86 0 +#endif + +#if !defined(COMPILER_CLANG) +# define COMPILER_CLANG 0 +#endif + +#if !defined(COMPILER_MSVC) +# define COMPILER_MSVC 0 +#endif + +#if !defined(OS_WINDOWS) +# define OS_WINDOWS 0 +#endif + +#if !defined(LANG_CPP) +# define LANG_CPP 0 +#endif + +#if !defined(LANG_C) +# define LANG_C 0 +#endif + +#if !ARCH_X64 +# error Trying to build unsupported architecture. Only x64 is supported. +#endif diff --git a/Juliet/core/core_entry_point.c b/Juliet/core/core_entry_point.c new file mode 100644 index 0000000..1c7465a --- /dev/null +++ b/Juliet/core/core_entry_point.c @@ -0,0 +1,6 @@ +internal void entry_point(); + +internal void main_thread_core_entry_point(int argc, char** argv) +{ + entry_point(); +} diff --git a/Juliet/core/core_entry_point.h b/Juliet/core/core_entry_point.h new file mode 100644 index 0000000..765a135 --- /dev/null +++ b/Juliet/core/core_entry_point.h @@ -0,0 +1,3 @@ +#pragma once + +internal void main_thread_core_entry_point(int argc, char** argv); diff --git a/Juliet/core/core_inc.c b/Juliet/core/core_inc.c new file mode 100644 index 0000000..48ce51c --- /dev/null +++ b/Juliet/core/core_inc.c @@ -0,0 +1,10 @@ +// clang-format off +#include "core_string.c" +#include "core_entry_point.c" +// clang-format on + +#if OS_WINDOWS +# include "win32/core/win32_core.c" +#elif +# error Unsupported OS +#endif diff --git a/Juliet/core/core_inc.h b/Juliet/core/core_inc.h new file mode 100644 index 0000000..398b883 --- /dev/null +++ b/Juliet/core/core_inc.h @@ -0,0 +1,15 @@ +#pragma once + +// clang-format off +#include "core_defines.h" + +#include "core.h" +#include "core_string.h" +#include "core_entry_point.h" +// clang-format on + +#if OS_WINDOWS +# include "win32/core/win32_core.h" +#elif +# error Unsupported OS +#endif diff --git a/Juliet/core/core_string.c b/Juliet/core/core_string.c new file mode 100644 index 0000000..f90aeae --- /dev/null +++ b/Juliet/core/core_string.c @@ -0,0 +1,61 @@ +// ========================================================================== // +// String Length +internal uint64 cstring8_length(uint8* cstring) +{ + uint64 length = 0; + if (cstring) + { + uint8* p = cstring; + for (; *p != '\0'; ++p) + ; + length = (uint64)(p - cstring); + } + return length; +} + +internal uint64 cstring16_length(uint16* cstring) +{ + uint64 length = 0; + if (cstring) + { + uint16* p = cstring; + for (; *p != '\0'; ++p) + ; + length = (uint64)(p - cstring); + } + return length; +} + +internal uint64 cstring32_length(uint32* cstring) +{ + uint64 length = 0; + if (cstring) + { + uint32* p = cstring; + for (; *p != '\0'; ++p) + ; + length = (uint64)(p - cstring); + } + return length; +} + +// ========================================================================== // +// String Constructor + +internal String8 str8_cstring(uint8* cstring) +{ + String8 result = { (uint8*)cstring, cstring8_length((uint8*)cstring) }; + return result; +} + +internal String16 str16_cstring(uint16* cstring) +{ + String16 result = { (uint16*)cstring, cstring16_length((uint16*)cstring) }; + return result; +} + +internal String32 str32_cstring(uint32* cstring) +{ + String32 result = { (uint32*)cstring, cstring32_length((uint32*)cstring) }; + return result; +} diff --git a/Juliet/core/core_string.h b/Juliet/core/core_string.h new file mode 100644 index 0000000..7ba7f17 --- /dev/null +++ b/Juliet/core/core_string.h @@ -0,0 +1,36 @@ +#pragma once + +// ========================================================================== // +// String Types + +typedef struct String8 String8; +struct String8 +{ + uint8* str; + uint64 size; +}; + +typedef struct String16 String16; +struct String16 +{ + uint16* str; + uint64 size; +}; + +typedef struct String32 String32; +struct String32 +{ + uint32* str; + uint64 size; +}; + +// ========================================================================== // +// String Length +internal uint64 cstring8_length(uint8* cstring); +internal uint64 cstring16_length(uint16* cstring); +internal uint64 cstring32_length(uint32* cstring); + +// ========================================================================== // +// String Constructor + +internal String16 str16_cstring(uint16* cstring); diff --git a/Juliet/win32/core/win32_core.c b/Juliet/win32/core/win32_core.c new file mode 100644 index 0000000..f8633c2 --- /dev/null +++ b/Juliet/win32/core/win32_core.c @@ -0,0 +1,17 @@ +internal void w32_entry_point(int argc, WCHAR** wargv) +{ + char* argv[10]; + for (int i = 0; i < argc; ++i) + { + String16 arg16 = str16_cstring((uint16*)wargv[i]); + String8 arg8 = str8_from_16(args_arena, arg16); + argv[i] = (char*)arg8.str; + } + main_thread_core_entry_point(argc, &argv); +} + +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + w32_entry_point(__argc, __wargv); + return 0; +} diff --git a/Juliet/win32/core/win32_core.h b/Juliet/win32/core/win32_core.h new file mode 100644 index 0000000..9475762 --- /dev/null +++ b/Juliet/win32/core/win32_core.h @@ -0,0 +1,4 @@ +#pragma once + +#include +#pragma comment(lib, "user32") diff --git a/misc/new_build.bat b/misc/new_build.bat new file mode 100644 index 0000000..c3cc9fa --- /dev/null +++ b/misc/new_build.bat @@ -0,0 +1,3 @@ +@echo off +call "%~dp0..\new_build.bat" %* +exit /b %ERRORLEVEL% diff --git a/misc/new_generate.bat b/misc/new_generate.bat new file mode 100644 index 0000000..f5eb64d --- /dev/null +++ b/misc/new_generate.bat @@ -0,0 +1,3 @@ +@echo off +call "%~dp0..\new_generate.bat" %* +exit /b %ERRORLEVEL% diff --git a/new_build.bat b/new_build.bat new file mode 100644 index 0000000..12e8799 --- /dev/null +++ b/new_build.bat @@ -0,0 +1,125 @@ +@echo off +setlocal enabledelayedexpansion +cd /D "%~dp0" + +:: --- Usage Notes ------------------------------------------------------------- +:: +:: Central build script for Juliet new module architecture (inspired by raddbg). +:: Takes alphanumeric arguments to control target, compiler, and build configuration. +:: +:: Examples: +:: new_build +:: new_build appbin +:: new_build appbin clang +:: new_build appbin release +:: new_build appbin msvc debug asan +:: new_build clean +:: + +:: --- Unpack Arguments ------------------------------------------------------- +for %%a in (%*) do set "%%~a=1" + +if "%clean%"=="1" ( + if exist build ( + echo [clean] Removing build directory... + rmdir /s /q build + ) + exit /b 0 +) + +:: --- Compiler Selection & Auto-Detection ------------------------------------- +if not "%msvc%"=="1" if not "%clang%"=="1" set clang=1 + +if "%msvc%"=="1" ( + where cl.exe >nul 2>nul + if errorlevel 1 ( + if exist "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" ( + call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 >nul 2>&1 + ) + ) +) + +if not "%release%"=="1" set debug=1 +if "%debug%"=="1" set release=0 && echo [debug mode] +if "%release%"=="1" set debug=0 && echo [release mode] +if "%clang%"=="1" set msvc=0 && echo [clang compile] +if "%msvc%"=="1" set clang=0 && echo [msvc compile] + +:: Default target: appbin +if "%~1"=="" echo [default mode, assuming `appbin` build] && set appbin=1 +if "%~1"=="release" if "%~2"=="" echo [default mode, assuming `appbin` build] && set appbin=1 +if "%~1"=="debug" if "%~2"=="" echo [default mode, assuming `appbin` build] && set appbin=1 +if "%~1"=="clang" if "%~2"=="" echo [default mode, assuming `appbin` build] && set appbin=1 +if "%~1"=="msvc" if "%~2"=="" echo [default mode, assuming `appbin` build] && set appbin=1 + +:: --- Unpack Command Line Build Arguments ------------------------------------ +set auto_compile_flags= +if "%asan%"=="1" set auto_compile_flags=%auto_compile_flags% -fsanitize=address && echo [asan enabled] +if "%ubsan%"=="1" set auto_compile_flags=%auto_compile_flags% -fsanitize=undefined && echo [ubsan enabled] + +:: --- Natvis Options --------------------------------------------------------- +set natvis_flags= +if exist "%~dp0Natvis\Juliet.natvis" ( + if "%msvc%"=="1" set natvis_flags=%natvis_flags% /NATVIS:"%~dp0Natvis\Juliet.natvis" + if "%clang%"=="1" set natvis_flags=%natvis_flags% -Xlinker /NATVIS:"%~dp0Natvis\Juliet.natvis" +) +if exist "%~dp0Natvis\String.natvis" ( + if "%msvc%"=="1" set natvis_flags=%natvis_flags% /NATVIS:"%~dp0Natvis\String.natvis" + if "%clang%"=="1" set natvis_flags=%natvis_flags% -Xlinker /NATVIS:"%~dp0Natvis\String.natvis" +) + +:: --- Compile/Link Line Definitions ------------------------------------------ +set cl_common= /I..\Juliet\ /nologo /FC /Z7 /Zc:preprocessor /utf-8 +set cl_debug= call cl /Od /Ob1 /DBUILD_DEBUG=1 %cl_common% %auto_compile_flags% +set cl_release= call cl /O2 /DBUILD_DEBUG=0 %cl_common% %auto_compile_flags% +set cl_link= /link /MANIFEST:EMBED /INCREMENTAL:NO /pdbaltpath:%%%%_PDB%%%% /DEBUG %natvis_flags% /opt:ref /opt:icf +set cl_out= /out: +set cl_obj_out= /Fo: +set cl_linker= + +set clang_common= -mcx16 -I..\Juliet\ -fdiagnostics-absolute-paths -Wall -Wno-unknown-warning-option -Wno-missing-braces -Wno-unused-function -Wno-unused-parameter -Wno-writable-strings -Wno-missing-field-initializers -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf -ferror-limit=10000 +set clang_debug= call clang -g -O0 -DBUILD_DEBUG=1 %clang_common% %auto_compile_flags% +set clang_release= call clang -g -O2 -DBUILD_DEBUG=0 %clang_common% %auto_compile_flags% +set clang_link= -fuse-ld=lld -Xlinker /MANIFEST:EMBED -Xlinker /pdbaltpath:%%%%_PDB%%%% -Xlinker /DEBUG %natvis_flags% -Xlinker /opt:ref -Xlinker /opt:noicf +set clang_out= -o +set clang_obj_out= -o +set clang_linker= -Xlinker + +:: --- Per-Build Settings ----------------------------------------------------- +if "%msvc%"=="1" set linker=%cl_linker% +if "%clang%"=="1" set linker=%clang_linker% +if "%msvc%"=="1" set only_compile=/c +if "%clang%"=="1" set only_compile=-c +if "%msvc%"=="1" set link_dll=/link /DLL +if "%clang%"=="1" set link_dll=-Xlinker -DLL + +:: --- Choose Compile/Link Lines ---------------------------------------------- +if "%msvc%"=="1" set compile_debug=%cl_debug% +if "%msvc%"=="1" set compile_release=%cl_release% +if "%msvc%"=="1" set compile_link=%cl_link% +if "%msvc%"=="1" set out=%cl_out% +if "%msvc%"=="1" set obj_out=%cl_obj_out% +if "%clang%"=="1" set compile_debug=%clang_debug% +if "%clang%"=="1" set compile_release=%clang_release% +if "%clang%"=="1" set compile_link=%clang_link% +if "%clang%"=="1" set out=%clang_out% +if "%clang%"=="1" set obj_out=%clang_obj_out% +if "%debug%"=="1" set compile=%compile_debug% +if "%release%"=="1" set compile=%compile_release% + +:: --- Prep Directories ------------------------------------------------------- +if not exist build mkdir build + +:: --- Build Targets (@build_targets) ----------------------------------------- +pushd build +if "%appbin%"=="1" set didbuild=1 && %compile% ..\Juliet\appbin\appbin_main.c %compile_link% %out%appbin.exe || exit /b 1 +popd + +:: --- Warn On No Builds ------------------------------------------------------ +if "%didbuild%"=="" ( + echo [WARNING] no valid build target specified; must use build target names like `new_build appbin`. + exit /b 1 +) + +echo [build success] +exit /b 0 diff --git a/new_generate.bat b/new_generate.bat new file mode 100644 index 0000000..09ae646 --- /dev/null +++ b/new_generate.bat @@ -0,0 +1,66 @@ +@echo off +setlocal enabledelayedexpansion +cd /D "%~dp0" + +:: --- Usage Notes ------------------------------------------------------------- +:: +:: Generates clangd compile_commands.json and Visual Studio project/solution files. +:: +:: Examples: +:: new_generate (generates compile_commands.json and VS files) +:: new_generate compile_commands (generates only compile_commands.json) +:: new_generate vs (generates only vs/new_Juliet.sln and projects) +:: + +set GEN_TOOL=misc\new_project_gen.exe +set GEN_SRC=tools\new_project_gen.c + +:: Auto-detect compiler for building the generator tool +set COMPILER= +where clang.exe >nul 2>nul +if not errorlevel 1 ( + set COMPILER=clang +) else ( + where cl.exe >nul 2>nul + if not errorlevel 1 ( + set COMPILER=cl + ) else ( + if exist "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" ( + call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 >nul 2>&1 + ) + where cl.exe >nul 2>nul + if not errorlevel 1 ( + set COMPILER=cl + ) + ) +) + +if "%COMPILER%"=="" ( + echo [ERROR] Neither clang nor cl found in PATH. + exit /b 1 +) + +:: Recompile generator tool if needed +set RECOMPILE=0 +if not exist "%GEN_TOOL%" set RECOMPILE=1 +if exist "Intermediate\new_project_gen.stamp" ( + set /p CACHED_STAMP=<"Intermediate\new_project_gen.stamp" + for %%I in ("%GEN_SRC%") do ( + if "%%~tI_%%~zI" neq "!CACHED_STAMP!" set RECOMPILE=1 + ) +) else set RECOMPILE=1 + +if "!RECOMPILE!"=="1" ( + echo [GENERATE] Compiling %GEN_SRC% -^> %GEN_TOOL%... + if not exist misc mkdir misc + if not exist Intermediate mkdir Intermediate + if "%COMPILER%"=="clang" ( + call clang -O2 -Wall -Wextra -Werror "%GEN_SRC%" -o "%GEN_TOOL%" || exit /b 1 + ) else ( + call cl /nologo /O2 /W4 /WX "%GEN_SRC%" /Fe"%GEN_TOOL%" || exit /b 1 + ) + for %%I in ("%GEN_SRC%") do (echo %%~tI_%%~zI)>"Intermediate\new_project_gen.stamp" +) + +"%GEN_TOOL%" %* +exit /b %ERRORLEVEL% diff --git a/tools/new_project_gen.c b/tools/new_project_gen.c new file mode 100644 index 0000000..b534cb0 --- /dev/null +++ b/tools/new_project_gen.c @@ -0,0 +1,745 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include +#include +#include + +#define Assert(c) do { if (!(c)) { fprintf(stderr, "Assertion failed: %s at %s:%d\n", #c, __FILE__, __LINE__); abort(); } } while(0) + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef int32_t int32; +typedef int64_t int64; + +typedef struct FileNode +{ + char path[512]; + struct FileNode* next; +} FileNode; + +typedef struct FileList +{ + FileNode* first; + FileNode* last; + int32 count; +} FileList; + +typedef struct ProjectInfo +{ + char name[64]; + char guid[64]; + FileList c_files; + FileList h_files; +} ProjectInfo; + +static void file_list_push(FileList* list, const char* path) +{ + Assert(list != NULL); + Assert(path != NULL); + + FileNode* node = (FileNode*)malloc(sizeof(FileNode)); + Assert(node != NULL); + + strncpy_s(node->path, sizeof(node->path), path, _TRUNCATE); + node->next = NULL; + + if (list->last == NULL) + { + list->first = node; + list->last = node; + } + else + { + list->last->next = node; + list->last = node; + } + list->count++; +} + +static void file_list_free(FileList* list) +{ + Assert(list != NULL); + + FileNode* curr = list->first; + while (curr != NULL) + { + FileNode* next = curr->next; + free(curr); + curr = next; + } + list->first = NULL; + list->last = NULL; + list->count = 0; +} + +static void normalize_path_slashes_forward(char* path) +{ + Assert(path != NULL); + + for (int32 i = 0; path[i] != '\0'; i++) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +} + +static void normalize_path_slashes_backward(char* path) +{ + Assert(path != NULL); + + for (int32 i = 0; path[i] != '\0'; i++) + { + if (path[i] == '/') + { + path[i] = '\\'; + } + } +} + +static bool str_ends_with_no_case(const char* str, const char* suffix) +{ + Assert(str != NULL); + Assert(suffix != NULL); + + size_t str_len = strlen(str); + size_t suffix_len = strlen(suffix); + if (suffix_len > str_len) + { + return false; + } + + const char* p = str + (str_len - suffix_len); + return (_stricmp(p, suffix) == 0); +} + +static bool is_c_source_file(const char* path) +{ + Assert(path != NULL); + return str_ends_with_no_case(path, ".c") || str_ends_with_no_case(path, ".cpp"); +} + +static bool is_c_header_file(const char* path) +{ + Assert(path != NULL); + return str_ends_with_no_case(path, ".h") || str_ends_with_no_case(path, ".hpp"); +} + +static void find_files_recursive(const char* dir_path, FileList* out_list, bool include_headers) +{ + Assert(dir_path != NULL); + Assert(out_list != NULL); + + char search_pattern[512]; + snprintf(search_pattern, sizeof(search_pattern), "%s\\*", dir_path); + + WIN32_FIND_DATAA find_data; + HANDLE find_handle = FindFirstFileA(search_pattern, &find_data); + if (find_handle == INVALID_HANDLE_VALUE) + { + return; + } + + do + { + if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) + { + continue; + } + + char item_path[512]; + snprintf(item_path, sizeof(item_path), "%s\\%s", dir_path, find_data.cFileName); + + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + find_files_recursive(item_path, out_list, include_headers); + } + else + { + if (is_c_source_file(item_path) || (include_headers && is_c_header_file(item_path))) + { + char clean_path[512]; + strncpy_s(clean_path, sizeof(clean_path), item_path, _TRUNCATE); + normalize_path_slashes_forward(clean_path); + file_list_push(out_list, clean_path); + } + } + } while (FindNextFileA(find_handle, &find_data)); + + FindClose(find_handle); +} + +static void generate_deterministic_guid(const char* input, char* out_guid, size_t out_guid_size) +{ + Assert(input != NULL); + Assert(out_guid != NULL); + Assert(out_guid_size >= 37); + + uint64 hash1 = 14695981039346656037ULL; + uint64 hash2 = 1099511628211ULL; + for (const char* c = input; *c != '\0'; ++c) + { + hash1 ^= (uint64)(*c); + hash1 *= 1099511628211ULL; + hash2 ^= (uint64)(*c); + hash2 *= 137ULL; + } + + snprintf(out_guid, out_guid_size, "%08X-%04X-%04X-%04X-%012llX", + (uint32)(hash1 >> 32), + (uint32)((hash1 >> 16) & 0xFFFF), + (uint32)(hash1 & 0xFFFF), + (uint32)((hash2 >> 48) & 0xFFFF), + (uint64)(hash2 & 0xFFFFFFFFFFFFULL)); +} + +static bool write_entire_file(const char* path, const char* buffer, size_t size) +{ + Assert(path != NULL); + Assert(buffer != NULL); + + HANDLE file_handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + { + fprintf(stderr, "[ERROR] Could not open file for writing: %s\n", path); + return false; + } + + DWORD bytes_written = 0; + BOOL success = WriteFile(file_handle, buffer, (DWORD)size, &bytes_written, NULL); + CloseHandle(file_handle); + + return (success && bytes_written == (DWORD)size); +} + +static void get_normalized_cwd(char* out_cwd, size_t out_cwd_size, char* out_real_cwd, size_t out_real_size) +{ + Assert(out_cwd != NULL); + Assert(out_cwd_size > 0); + + GetCurrentDirectoryA((DWORD)out_cwd_size, out_cwd); + normalize_path_slashes_forward(out_cwd); + + if (out_real_cwd != NULL && out_real_size > 0) + { + out_real_cwd[0] = '\0'; + HANDLE dir_handle = CreateFileA(".", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (dir_handle != INVALID_HANDLE_VALUE) + { + char real_path[512]; + DWORD len = GetFinalPathNameByHandleA(dir_handle, real_path, sizeof(real_path), FILE_NAME_NORMALIZED); + CloseHandle(dir_handle); + if (len > 0 && len < sizeof(real_path)) + { + const char* p = real_path; + if (strncmp(p, "\\\\?\\", 4) == 0) + { + p += 4; + } + strncpy_s(out_real_cwd, out_real_size, p, _TRUNCATE); + normalize_path_slashes_forward(out_real_cwd); + } + } + } +} + +static void generate_compile_commands(void) +{ + char cwd[512]; + char real_cwd[512]; + get_normalized_cwd(cwd, sizeof(cwd), real_cwd, sizeof(real_cwd)); + + size_t buf_cap = 8 * 1024 * 1024; + char* json_buf = (char*)malloc(buf_cap); + Assert(json_buf != NULL); + + int32 offset = 0; + offset += snprintf(json_buf + offset, buf_cap - offset, "[\n"); + + bool is_first = true; + + // 1. Scan new Juliet modules (core, appbin, etc.) + // We scan folders directly inside Juliet/, ignoring legacy 'src' and 'include' + WIN32_FIND_DATAA find_data; + HANDLE find_handle = FindFirstFileA("Juliet\\*", &find_data); + if (find_handle != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) + { + continue; + } + + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (_stricmp(find_data.cFileName, "src") == 0 || _stricmp(find_data.cFileName, "include") == 0) + { + continue; + } + + char module_dir[512]; + snprintf(module_dir, sizeof(module_dir), "Juliet\\%s", find_data.cFileName); + + FileList module_files = {0}; + find_files_recursive(module_dir, &module_files, false); + + char module_name[64]; + strncpy_s(module_name, sizeof(module_name), find_data.cFileName, _TRUNCATE); + + char module_inc_flag[128] = ""; + char module_inc_disk_path[512]; + snprintf(module_inc_disk_path, sizeof(module_inc_disk_path), "Juliet\\%s\\%s_inc.h", module_name, module_name); + if (_stricmp(module_name, "core") != 0 && GetFileAttributesA(module_inc_disk_path) != INVALID_FILE_ATTRIBUTES) + { + snprintf(module_inc_flag, sizeof(module_inc_flag), " \"/FI%s/%s_inc.h\",\n", module_name, module_name); + } + + for (FileNode* node = module_files.first; node != NULL; node = node->next) + { + const char* dirs_to_emit[2] = { cwd, (real_cwd[0] && _stricmp(cwd, real_cwd) != 0) ? real_cwd : NULL }; + + for (int32 d = 0; d < 2; d++) + { + if (dirs_to_emit[d] == NULL) + { + continue; + } + + if (!is_first) + { + offset += snprintf(json_buf + offset, buf_cap - offset, ",\n"); + } + is_first = false; + + char full_file_path[512]; + snprintf(full_file_path, sizeof(full_file_path), "%s/%s", dirs_to_emit[d], node->path); + + offset += snprintf(json_buf + offset, buf_cap - offset, + " {\n" + " \"directory\": \"%s\",\n" + " \"file\": \"%s\",\n" + " \"arguments\": [\n" + " \"clang-cl.exe\",\n" + " \"--driver-mode=cl\",\n" + " \"/nologo\",\n" + " \"/clang:-std=c11\",\n" + " \"/W4\",\n" + " \"/Wno-unused-function\",\n" + " \"/utf-8\",\n" + " \"/DBUILD_DEBUG=1\",\n" + " \"/IJuliet\",\n" + " \"/FIcore/core_inc.h\",\n" + "%s" + " \"-c\",\n" + " \"%s\"\n" + " ]\n" + " }", + dirs_to_emit[d], full_file_path, module_inc_flag, full_file_path); + } + } + + file_list_free(&module_files); + } + } while (FindNextFileA(find_handle, &find_data)); + + FindClose(find_handle); + } + + // 2. Scan legacy directories so existing files keep working with Neovim LSP + const char* legacy_dirs[] = { + "Juliet/src", + "Game", + "JulietApp", + "JulietShaderCompiler", + "External/imgui" + }; + + for (size_t l = 0; l < sizeof(legacy_dirs) / sizeof(legacy_dirs[0]); l++) + { + char search_path[512]; + strncpy_s(search_path, sizeof(search_path), legacy_dirs[l], _TRUNCATE); + normalize_path_slashes_backward(search_path); + + DWORD attrs = GetFileAttributesA(search_path); + if (attrs == INVALID_FILE_ATTRIBUTES || !(attrs & FILE_ATTRIBUTE_DIRECTORY)) + { + continue; + } + + FileList legacy_files = {0}; + find_files_recursive(search_path, &legacy_files, false); + + for (FileNode* node = legacy_files.first; node != NULL; node = node->next) + { + const char* dirs_to_emit[2] = { cwd, (real_cwd[0] && _stricmp(cwd, real_cwd) != 0) ? real_cwd : NULL }; + + for (int32 d = 0; d < 2; d++) + { + if (dirs_to_emit[d] == NULL) + { + continue; + } + + if (!is_first) + { + offset += snprintf(json_buf + offset, buf_cap - offset, ",\n"); + } + is_first = false; + + char full_file_path[512]; + snprintf(full_file_path, sizeof(full_file_path), "%s/%s", dirs_to_emit[d], node->path); + const char* lang_flag = str_ends_with_no_case(full_file_path, ".c") ? "/clang:-std=c99" : "/std:c++20"; + + offset += snprintf(json_buf + offset, buf_cap - offset, + " {\n" + " \"directory\": \"%s\",\n" + " \"file\": \"%s\",\n" + " \"arguments\": [\n" + " \"clang-cl.exe\",\n" + " \"--driver-mode=cl\",\n" + " \"/nologo\",\n" + " \"%s\",\n" + " \"/W4\",\n" + " \"/utf-8\",\n" + " \"/DJULIET_WIN32\",\n" + " \"/DUNICODE\",\n" + " \"/D_UNICODE\",\n" + " \"/DWIN32_LEAN_AND_MEAN\",\n" + " \"/D_CRT_SECURE_NO_WARNINGS\",\n" + " \"/DDEBUG\",\n" + " \"/DJULIET_DEBUG\",\n" + " \"/DPROFILING_ENABLED\",\n" + " \"/DJULIET_ENABLE_IMGUI\",\n" + " \"/DIMGUI_DEFINE_MATH_OPERATORS\",\n" + " \"/DJULIET_EXPORT\",\n" + " \"/DIMGUI_API=__declspec(dllexport)\",\n" + " \"/IJuliet/include\",\n" + " \"/IJuliet/src\",\n" + " \"/IJuliet\",\n" + " \"/IGame\",\n" + " \"/IExternal/imgui\",\n" + " \"/IExternal/imgui/backends\",\n" + " \"/FICore/PCH.h\",\n" + " \"-c\",\n" + " \"%s\"\n" + " ]\n" + " }", + dirs_to_emit[d], full_file_path, lang_flag, full_file_path); + } + } + + file_list_free(&legacy_files); + } + + offset += snprintf(json_buf + offset, buf_cap - offset, "\n]\n"); + + bool success = write_entire_file("compile_commands.json", json_buf, offset); + if (success) + { + printf("[SUCCESS] Generated compile_commands.json\n"); + } + + free(json_buf); +} + +static void generate_vs_files(void) +{ + CreateDirectoryA("vs", NULL); + + // Discover modules in Juliet/ + ProjectInfo appbin_project = {0}; + strncpy_s(appbin_project.name, sizeof(appbin_project.name), "appbin", _TRUNCATE); + generate_deterministic_guid(appbin_project.name, appbin_project.guid, sizeof(appbin_project.guid)); + + // Gather all new module files for appbin project + WIN32_FIND_DATAA find_data; + HANDLE find_handle = FindFirstFileA("Juliet\\*", &find_data); + if (find_handle != INVALID_HANDLE_VALUE) + { + do + { + if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) + { + continue; + } + + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (_stricmp(find_data.cFileName, "src") == 0 || _stricmp(find_data.cFileName, "include") == 0) + { + continue; + } + + char module_dir[512]; + snprintf(module_dir, sizeof(module_dir), "Juliet\\%s", find_data.cFileName); + + FileList all_files = {0}; + find_files_recursive(module_dir, &all_files, true); + + for (FileNode* node = all_files.first; node != NULL; node = node->next) + { + if (is_c_source_file(node->path)) + { + file_list_push(&appbin_project.c_files, node->path); + } + else if (is_c_header_file(node->path)) + { + file_list_push(&appbin_project.h_files, node->path); + } + } + + file_list_free(&all_files); + } + } while (FindNextFileA(find_handle, &find_data)); + + FindClose(find_handle); + } + + // 1. Generate vs/appbin.vcxproj + size_t buf_cap = 2 * 1024 * 1024; + char* vcxproj_buf = (char*)malloc(buf_cap); + Assert(vcxproj_buf != NULL); + + int32 offset = 0; + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, + "\n" + "\n" + " \n" + " \n" + " Debug\n" + " x64\n" + " \n" + " \n" + " Release\n" + " x64\n" + " \n" + " \n" + " \n" + " {%s}\n" + " MakeFileProj\n" + " appbin\n" + " \n" + " \n" + " \n" + " Makefile\n" + " true\n" + " v143\n" + " \n" + " \n" + " Makefile\n" + " false\n" + " v143\n" + " \n" + " \n" + " \n" + " \n" + " cd $(SolutionDir)..\\ & new_build.bat appbin debug\n" + " cd $(SolutionDir)..\\ & new_build.bat clean & new_build.bat appbin debug\n" + " cd $(SolutionDir)..\\ & new_build.bat clean\n" + " $(SolutionDir)..\\build\\appbin.exe\n" + " $(SolutionDir)..\\Juliet;$(NMakeIncludeSearchPath)\n" + " BUILD_DEBUG=1;$(NMakePreprocessorDefinitions)\n" + " \n" + " \n" + " cd $(SolutionDir)..\\ & new_build.bat appbin release\n" + " cd $(SolutionDir)..\\ & new_build.bat clean & new_build.bat appbin release\n" + " cd $(SolutionDir)..\\ & new_build.bat clean\n" + " $(SolutionDir)..\\build\\appbin.exe\n" + " $(SolutionDir)..\\Juliet;$(NMakeIncludeSearchPath)\n" + " BUILD_DEBUG=0;$(NMakePreprocessorDefinitions)\n" + " \n" + " \n", + appbin_project.guid); + + for (FileNode* node = appbin_project.c_files.first; node != NULL; node = node->next) + { + char backslash_path[512]; + strncpy_s(backslash_path, sizeof(backslash_path), node->path, _TRUNCATE); + normalize_path_slashes_backward(backslash_path); + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, + " \n", backslash_path); + } + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, " \n \n"); + + for (FileNode* node = appbin_project.h_files.first; node != NULL; node = node->next) + { + char backslash_path[512]; + strncpy_s(backslash_path, sizeof(backslash_path), node->path, _TRUNCATE); + normalize_path_slashes_backward(backslash_path); + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, + " \n", backslash_path); + } + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, " \n"); + + if (GetFileAttributesA("Natvis") != INVALID_FILE_ATTRIBUTES) + { + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, + " \n" + " \n" + " \n" + " \n"); + } + + offset += snprintf(vcxproj_buf + offset, buf_cap - offset, + " \n" + "\n"); + + write_entire_file("vs\\appbin.vcxproj", vcxproj_buf, offset); + printf("[SUCCESS] Generated vs\\appbin.vcxproj\n"); + free(vcxproj_buf); + + // 2. Generate vs/appbin.vcxproj.filters + char* filters_buf = (char*)malloc(buf_cap); + Assert(filters_buf != NULL); + + int32 f_offset = 0; + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n"); + + for (FileNode* node = appbin_project.c_files.first; node != NULL; node = node->next) + { + char backslash_path[512]; + strncpy_s(backslash_path, sizeof(backslash_path), node->path, _TRUNCATE); + normalize_path_slashes_backward(backslash_path); + + const char* filter = strstr(node->path, "core/") ? "core" : "appbin"; + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, + " \n" + " %s\n" + " \n", backslash_path, filter); + } + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, " \n \n"); + + for (FileNode* node = appbin_project.h_files.first; node != NULL; node = node->next) + { + char backslash_path[512]; + strncpy_s(backslash_path, sizeof(backslash_path), node->path, _TRUNCATE); + normalize_path_slashes_backward(backslash_path); + + const char* filter = strstr(node->path, "core/") ? "core" : "appbin"; + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, + " \n" + " %s\n" + " \n", backslash_path, filter); + } + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, " \n"); + + if (GetFileAttributesA("Natvis") != INVALID_FILE_ATTRIBUTES) + { + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, + " \n" + " \n" + " \n" + " \n" + " \n" + " Natvis\n" + " \n" + " \n" + " Natvis\n" + " \n" + " \n"); + } + + f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, "\n"); + + write_entire_file("vs\\appbin.vcxproj.filters", filters_buf, f_offset); + printf("[SUCCESS] Generated vs\\appbin.vcxproj.filters\n"); + free(filters_buf); + + // 3. Generate vs/new_Juliet.sln + char sln_buf[4096]; + int32 s_offset = snprintf(sln_buf, sizeof(sln_buf), + "Microsoft Visual Studio Solution File, Format Version 12.00\n" + "# Visual Studio Version 17\n" + "VisualStudioVersion = 17.0.31903.59\n" + "MinimumVisualStudioVersion = 10.0.40219.1\n" + "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"appbin\", \"appbin.vcxproj\", \"{%s}\"\n" + "EndProject\n" + "Global\n" + " GlobalSection(SolutionConfigurationPlatforms) = preSolution\n" + " Debug|x64 = Debug|x64\n" + " Release|x64 = Release|x64\n" + " EndGlobalSection\n" + " GlobalSection(ProjectConfigurationPlatforms) = postSolution\n" + " {%s}.Debug|x64.ActiveCfg = Debug|x64\n" + " {%s}.Debug|x64.Build.0 = Debug|x64\n" + " {%s}.Release|x64.ActiveCfg = Release|x64\n" + " {%s}.Release|x64.Build.0 = Release|x64\n" + " EndGlobalSection\n" + " GlobalSection(SolutionProperties) = preSolution\n" + " HideSolutionNode = FALSE\n" + " EndGlobalSection\n" + "EndGlobal\n", + appbin_project.guid, + appbin_project.guid, + appbin_project.guid, + appbin_project.guid, + appbin_project.guid); + + write_entire_file("vs\\new_Juliet.sln", sln_buf, s_offset); + printf("[SUCCESS] Generated vs\\new_Juliet.sln\n"); + + file_list_free(&appbin_project.c_files); + file_list_free(&appbin_project.h_files); +} + +int main(int argc, char** argv) +{ + bool do_compile_commands = false; + bool do_vs = false; + + if (argc <= 1) + { + do_compile_commands = true; + do_vs = true; + } + else + { + for (int i = 1; i < argc; i++) + { + if (_stricmp(argv[i], "all") == 0) + { + do_compile_commands = true; + do_vs = true; + } + else if (_stricmp(argv[i], "compile_commands") == 0 || _stricmp(argv[i], "json") == 0 || _stricmp(argv[i], "lsp") == 0) + { + do_compile_commands = true; + } + else if (_stricmp(argv[i], "vs") == 0 || _stricmp(argv[i], "sln") == 0 || _stricmp(argv[i], "vcxproj") == 0) + { + do_vs = true; + } + else + { + printf("[INFO] Unknown argument '%s', generating all by default.\n", argv[i]); + do_compile_commands = true; + do_vs = true; + } + } + } + + if (do_compile_commands) + { + generate_compile_commands(); + } + + if (do_vs) + { + generate_vs_files(); + } + + return 0; +}