1011 lines
35 KiB
C
1011 lines
35 KiB
C
#define _CRT_SECURE_NO_WARNINGS
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#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 int32 get_layer_order_from_main(char layers[32][64])
|
|
{
|
|
int32 count = 0;
|
|
FILE* file = fopen("Juliet/appbin/appbin_main.c", "rb");
|
|
if (file != NULL)
|
|
{
|
|
char line[512];
|
|
while (fgets(line, sizeof(line), file) != NULL)
|
|
{
|
|
char* inc_pos = strstr(line, "#include");
|
|
if (inc_pos != NULL)
|
|
{
|
|
char* quote_start = strchr(inc_pos, '"');
|
|
if (quote_start != NULL)
|
|
{
|
|
quote_start++;
|
|
char* slash = strchr(quote_start, '/');
|
|
if (slash == NULL)
|
|
{
|
|
slash = strchr(quote_start, '\\');
|
|
}
|
|
if (slash != NULL)
|
|
{
|
|
size_t layer_len = (size_t)(slash - quote_start);
|
|
if (layer_len > 0 && layer_len < 64)
|
|
{
|
|
char layer_name[64];
|
|
memcpy(layer_name, quote_start, layer_len);
|
|
layer_name[layer_len] = '\0';
|
|
|
|
char* quote_end = strchr(slash, '"');
|
|
if (quote_end != NULL && strstr(slash, "_inc.h") != NULL)
|
|
{
|
|
bool already_added = false;
|
|
for (int32 i = 0; i < count; ++i)
|
|
{
|
|
if (_stricmp(layers[i], layer_name) == 0)
|
|
{
|
|
already_added = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!already_added && count < 32)
|
|
{
|
|
strncpy_s(layers[count], sizeof(layers[count]), layer_name, _TRUNCATE);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
// Always ensure "core" is present
|
|
bool has_core = false;
|
|
for (int32 i = 0; i < count; ++i)
|
|
{
|
|
if (_stricmp(layers[i], "core") == 0)
|
|
{
|
|
has_core = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_core && count < 32)
|
|
{
|
|
for (int32 i = count; i > 0; --i)
|
|
{
|
|
strncpy_s(layers[i], sizeof(layers[i]), layers[i - 1], _TRUNCATE);
|
|
}
|
|
strncpy_s(layers[0], sizeof(layers[0]), "core", _TRUNCATE);
|
|
count++;
|
|
}
|
|
|
|
// Discover any other directories in Juliet/ that have <dir>_inc.h but weren't in appbin_main.c yet
|
|
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 ||
|
|
_stricmp(find_data.cFileName, "appbin") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
bool already = false;
|
|
for (int32 i = 0; i < count; ++i)
|
|
{
|
|
if (_stricmp(layers[i], find_data.cFileName) == 0)
|
|
{
|
|
already = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!already && count < 32)
|
|
{
|
|
strncpy_s(layers[count], sizeof(layers[count]), find_data.cFileName, _TRUNCATE);
|
|
count++;
|
|
}
|
|
}
|
|
} while (FindNextFileA(find_handle, &find_data));
|
|
FindClose(find_handle);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static int32 find_layer_index_for_file(const char* file_path, char layers[32][64], int32 layer_count)
|
|
{
|
|
Assert(file_path != NULL);
|
|
|
|
if (strstr(file_path, "win32/core") != NULL || strstr(file_path, "win32\\core") != NULL)
|
|
{
|
|
for (int32 i = 0; i < layer_count; ++i)
|
|
{
|
|
if (_stricmp(layers[i], "core") == 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 i = 0; i < layer_count; ++i)
|
|
{
|
|
char prefix1[128];
|
|
char prefix2[128];
|
|
snprintf(prefix1, sizeof(prefix1), "Juliet/%s/", layers[i]);
|
|
snprintf(prefix2, sizeof(prefix2), "Juliet\\%s\\", layers[i]);
|
|
if (_strnicmp(file_path, prefix1, strlen(prefix1)) == 0 ||
|
|
_strnicmp(file_path, prefix2, strlen(prefix2)) == 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void get_preceding_includes_from_inc_h(const char* layer_name, const char* target_file_path, char* out_flags, size_t out_flags_size)
|
|
{
|
|
Assert(layer_name != NULL);
|
|
Assert(target_file_path != NULL);
|
|
Assert(out_flags != NULL);
|
|
|
|
char inc_h_path[512];
|
|
snprintf(inc_h_path, sizeof(inc_h_path), "Juliet/%s/%s_inc.h", layer_name, layer_name);
|
|
|
|
FILE* file = fopen(inc_h_path, "rb");
|
|
if (file == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char line[512];
|
|
while (fgets(line, sizeof(line), file) != NULL)
|
|
{
|
|
char* hash = strchr(line, '#');
|
|
if (hash == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
char* inc_pos = strstr(hash, "include");
|
|
if (inc_pos == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char* quote_start = strchr(inc_pos, '"');
|
|
if (quote_start == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
quote_start++;
|
|
char* quote_end = strchr(quote_start, '"');
|
|
if (quote_end == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
size_t inc_file_len = (size_t)(quote_end - quote_start);
|
|
if (inc_file_len == 0 || inc_file_len >= 256)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char inc_file[256];
|
|
memcpy(inc_file, quote_start, inc_file_len);
|
|
inc_file[inc_file_len] = '\0';
|
|
normalize_path_slashes_forward(inc_file);
|
|
|
|
char full_inc_path[512];
|
|
if (strstr(inc_file, "/") != NULL)
|
|
{
|
|
snprintf(full_inc_path, sizeof(full_inc_path), "Juliet/%s", inc_file);
|
|
}
|
|
else
|
|
{
|
|
snprintf(full_inc_path, sizeof(full_inc_path), "Juliet/%s/%s", layer_name, inc_file);
|
|
}
|
|
normalize_path_slashes_forward(full_inc_path);
|
|
|
|
char expected_suffix[256];
|
|
snprintf(expected_suffix, sizeof(expected_suffix), "/%s", inc_file);
|
|
|
|
if (_stricmp(full_inc_path, target_file_path) == 0 ||
|
|
str_ends_with_no_case(target_file_path, expected_suffix))
|
|
{
|
|
break;
|
|
}
|
|
|
|
char flag[512];
|
|
if (strstr(inc_file, "/") != NULL)
|
|
{
|
|
snprintf(flag, sizeof(flag), " \"/FI%s\",\n", inc_file);
|
|
}
|
|
else
|
|
{
|
|
snprintf(flag, sizeof(flag), " \"/FI%s/%s\",\n", layer_name, inc_file);
|
|
}
|
|
strncat_s(out_flags, out_flags_size, flag, _TRUNCATE);
|
|
}
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
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;
|
|
|
|
char layers[32][64];
|
|
int32 layer_count = get_layer_order_from_main(layers);
|
|
|
|
// 1. Scan new Juliet modules (core, render, ui, appbin, etc.)
|
|
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, true);
|
|
|
|
for (FileNode* node = module_files.first; node != NULL; node = node->next)
|
|
{
|
|
char forced_inc_flags[1024] = "";
|
|
int32 file_layer_idx = find_layer_index_for_file(node->path, layers, layer_count);
|
|
|
|
if (file_layer_idx >= 0 && _stricmp(layers[file_layer_idx], "appbin") != 0)
|
|
{
|
|
for (int32 i = 0; i <= file_layer_idx; ++i)
|
|
{
|
|
if (i < file_layer_idx)
|
|
{
|
|
char inc_disk_path[512];
|
|
snprintf(inc_disk_path, sizeof(inc_disk_path), "Juliet\\%s\\%s_inc.h", layers[i], layers[i]);
|
|
if (GetFileAttributesA(inc_disk_path) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
char flag[128];
|
|
snprintf(flag, sizeof(flag), " \"/FI%s/%s_inc.h\",\n", layers[i], layers[i]);
|
|
strncat_s(forced_inc_flags, sizeof(forced_inc_flags), flag, _TRUNCATE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (is_c_source_file(node->path))
|
|
{
|
|
char inc_disk_path[512];
|
|
snprintf(inc_disk_path, sizeof(inc_disk_path), "Juliet\\%s\\%s_inc.h", layers[i], layers[i]);
|
|
if (GetFileAttributesA(inc_disk_path) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
char flag[128];
|
|
snprintf(flag, sizeof(flag), " \"/FI%s/%s_inc.h\",\n", layers[i], layers[i]);
|
|
strncat_s(forced_inc_flags, sizeof(forced_inc_flags), flag, _TRUNCATE);
|
|
}
|
|
}
|
|
else if (!str_ends_with_no_case(node->path, "_inc.h"))
|
|
{
|
|
get_preceding_includes_from_inc_h(layers[i], node->path, forced_inc_flags, sizeof(forced_inc_flags));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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"
|
|
" \"/clang:-Wno-initializer-overrides\",\n"
|
|
" \"/utf-8\",\n"
|
|
" \"/DBUILD_DEBUG=1\",\n"
|
|
" \"/IJuliet\",\n"
|
|
"%s"
|
|
" \"-c\",\n"
|
|
" \"%s\"\n"
|
|
" ]\n"
|
|
" }",
|
|
dirs_to_emit[d], full_file_path, forced_inc_flags, 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,
|
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
"<Project DefaultTargets=\"Build\" ToolsVersion=\"17.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"
|
|
" <ItemGroup Label=\"ProjectConfigurations\">\n"
|
|
" <ProjectConfiguration Include=\"Debug|x64\">\n"
|
|
" <Configuration>Debug</Configuration>\n"
|
|
" <Platform>x64</Platform>\n"
|
|
" </ProjectConfiguration>\n"
|
|
" <ProjectConfiguration Include=\"Release|x64\">\n"
|
|
" <Configuration>Release</Configuration>\n"
|
|
" <Platform>x64</Platform>\n"
|
|
" </ProjectConfiguration>\n"
|
|
" </ItemGroup>\n"
|
|
" <PropertyGroup Label=\"Globals\">\n"
|
|
" <ProjectGuid>{%s}</ProjectGuid>\n"
|
|
" <Keyword>MakeFileProj</Keyword>\n"
|
|
" <ProjectName>appbin</ProjectName>\n"
|
|
" </PropertyGroup>\n"
|
|
" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n"
|
|
" <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n"
|
|
" <ConfigurationType>Makefile</ConfigurationType>\n"
|
|
" <UseDebugLibraries>true</UseDebugLibraries>\n"
|
|
" <PlatformToolset>v143</PlatformToolset>\n"
|
|
" </PropertyGroup>\n"
|
|
" <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n"
|
|
" <ConfigurationType>Makefile</ConfigurationType>\n"
|
|
" <UseDebugLibraries>false</UseDebugLibraries>\n"
|
|
" <PlatformToolset>v143</PlatformToolset>\n"
|
|
" </PropertyGroup>\n"
|
|
" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n"
|
|
" <ImportGroup Label=\"ExtensionSettings\" />\n"
|
|
" <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n"
|
|
" <NMakeBuildCommandLine>cd $(SolutionDir)..\\ & new_build.bat appbin debug</NMakeBuildCommandLine>\n"
|
|
" <NMakeReBuildCommandLine>cd $(SolutionDir)..\\ & new_build.bat clean & new_build.bat appbin debug</NMakeReBuildCommandLine>\n"
|
|
" <NMakeCleanCommandLine>cd $(SolutionDir)..\\ & new_build.bat clean</NMakeCleanCommandLine>\n"
|
|
" <NMakeOutput>$(SolutionDir)..\\build\\appbin.exe</NMakeOutput>\n"
|
|
" <NMakeIncludeSearchPath>$(SolutionDir)..\\Juliet;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>\n"
|
|
" <NMakePreprocessorDefinitions>BUILD_DEBUG=1;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>\n"
|
|
" </PropertyGroup>\n"
|
|
" <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n"
|
|
" <NMakeBuildCommandLine>cd $(SolutionDir)..\\ & new_build.bat appbin release</NMakeBuildCommandLine>\n"
|
|
" <NMakeReBuildCommandLine>cd $(SolutionDir)..\\ & new_build.bat clean & new_build.bat appbin release</NMakeReBuildCommandLine>\n"
|
|
" <NMakeCleanCommandLine>cd $(SolutionDir)..\\ & new_build.bat clean</NMakeCleanCommandLine>\n"
|
|
" <NMakeOutput>$(SolutionDir)..\\build\\appbin.exe</NMakeOutput>\n"
|
|
" <NMakeIncludeSearchPath>$(SolutionDir)..\\Juliet;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>\n"
|
|
" <NMakePreprocessorDefinitions>BUILD_DEBUG=0;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>\n"
|
|
" </PropertyGroup>\n"
|
|
" <ItemGroup>\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,
|
|
" <ClCompile Include=\"..\\%s\" />\n", backslash_path);
|
|
}
|
|
offset += snprintf(vcxproj_buf + offset, buf_cap - offset, " </ItemGroup>\n <ItemGroup>\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,
|
|
" <ClInclude Include=\"..\\%s\" />\n", backslash_path);
|
|
}
|
|
offset += snprintf(vcxproj_buf + offset, buf_cap - offset, " </ItemGroup>\n");
|
|
|
|
if (GetFileAttributesA("Natvis") != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
offset += snprintf(vcxproj_buf + offset, buf_cap - offset,
|
|
" <ItemGroup>\n"
|
|
" <Natvis Include=\"..\\Natvis\\Juliet.natvis\" />\n"
|
|
" <Natvis Include=\"..\\Natvis\\String.natvis\" />\n"
|
|
" </ItemGroup>\n");
|
|
}
|
|
|
|
offset += snprintf(vcxproj_buf + offset, buf_cap - offset,
|
|
" <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n"
|
|
"</Project>\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,
|
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
"<Project ToolsVersion=\"17.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"
|
|
" <ItemGroup>\n"
|
|
" <Filter Include=\"core\" />\n"
|
|
" <Filter Include=\"appbin\" />\n"
|
|
" </ItemGroup>\n"
|
|
" <ItemGroup>\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,
|
|
" <ClCompile Include=\"..\\%s\">\n"
|
|
" <Filter>%s</Filter>\n"
|
|
" </ClCompile>\n", backslash_path, filter);
|
|
}
|
|
f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, " </ItemGroup>\n <ItemGroup>\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,
|
|
" <ClInclude Include=\"..\\%s\">\n"
|
|
" <Filter>%s</Filter>\n"
|
|
" </ClInclude>\n", backslash_path, filter);
|
|
}
|
|
f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, " </ItemGroup>\n");
|
|
|
|
if (GetFileAttributesA("Natvis") != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset,
|
|
" <ItemGroup>\n"
|
|
" <Filter Include=\"Natvis\" />\n"
|
|
" </ItemGroup>\n"
|
|
" <ItemGroup>\n"
|
|
" <Natvis Include=\"..\\Natvis\\Juliet.natvis\">\n"
|
|
" <Filter>Natvis</Filter>\n"
|
|
" </Natvis>\n"
|
|
" <Natvis Include=\"..\\Natvis\\String.natvis\">\n"
|
|
" <Filter>Natvis</Filter>\n"
|
|
" </Natvis>\n"
|
|
" </ItemGroup>\n");
|
|
}
|
|
|
|
f_offset += snprintf(filters_buf + f_offset, buf_cap - f_offset, "</Project>\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;
|
|
}
|