/* TODO: Move decls to meta.h */ #define MetaRebuildCode 1317212284 //////////////////////////////// //~ Default base layer compiler definitions #ifndef IsConsoleApp # define IsConsoleApp 1 #endif #ifndef RtcIsEnabled # define RtcIsEnabled 1 #endif #ifndef UnoptimizedIsEnabled # define UnoptimizedIsEnabled 1 #endif #ifndef AsanIsEnabled # define AsanIsEnabled 0 #endif #ifndef CrtlibIsEnabled # define CrtlibIsEnabled 1 #endif #ifndef DebinfoEnabled # define DebinfoEnabled 1 #endif #ifndef DeveloperIsEnabled # define DeveloperIsEnabled 1 #endif #ifndef ProfilingIsEnabled # define ProfilingIsEnabled 0 #endif #ifndef UnoptimizedIsEnabled # define UnoptimizedIsEnabled 1 #endif #ifndef TestsAreEnabled # define TestsAreEnabled 0 #endif //////////////////////////////// //~ Includes //- Header files #include "../base/base_inc.h" #include "meta_os/meta_os_inc.h" #include "meta_file/meta_file_inc.h" #include "meta_lay.h" #include "meta.h" //- Source files #include "meta_lay.c" //////////////////////////////// //~ Util /* TODO: Move this to OS layer */ void Echo(String msg) { HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); if (console_handle != INVALID_HANDLE_VALUE) { WriteFile(console_handle, msg.text, msg.len, 0, 0); } } void EchoLine(String msg) { HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); if (console_handle != INVALID_HANDLE_VALUE) { WriteFile(console_handle, msg.text, msg.len, 0, 0); WriteFile(console_handle, "\n", 1, 0, 0); } } Struct(LineCol) { i64 line; i64 col; }; LineCol LineColFromPos(String data, i64 pos) { TempArena scratch = BeginScratchNoConflict(); LineCol result = ZI; for (u64 cur = 0; cur < data.len && cur <= pos; ++cur) { u8 c = data.text[cur]; if (c == '\n') { ++result.line; result.col = 0; } else if (c != '\r') { ++result.col; } } result.line += 1; EndScratch(scratch); return result; } //////////////////////////////// //~ Build void StartupMeta(void) { Arena *arena = AcquireArena(Gibi(64)); M_ErrorList errors = ZI; i32 ret = 0; //////////////////////////////// //~ Dirty check //- Return rebuild code if metaprogram is dirty { /* Read old metahash */ u64 old_metahash = 0; if (F_IsFile(Lit("metahash.dat"))) { String hashes_str = F_DataFromFile(arena, Lit("metahash.dat")); if (hashes_str.len == sizeof(old_metahash)) { CopyBytes(&old_metahash, hashes_str.text, sizeof(old_metahash)); } OS_Rm(Lit("metahash.dat")); } /* Calculate new metahash */ u64 new_metahash = 0; { StringList check_files = ZI; F_FilesFromDir(arena, &check_files, Lit("../src/prof"), F_IterFlag_Recurse); F_FilesFromDir(arena, &check_files, Lit("../src/base"), F_IterFlag_Recurse); F_FilesFromDir(arena, &check_files, Lit("../src/meta"), F_IterFlag_Recurse); PushStringToList(arena, &check_files, Lit("../src/config.h")); for (StringListNode *n = check_files.first; n; n = n->next) { String file = n->s; new_metahash = RandU64FromSeeds(HashFnv64(new_metahash, file), OS_LastWriteTimestampFromPath(file)); } } /* Exit if metaprogram needs recompilation */ if (old_metahash == 0 || old_metahash == new_metahash) { F_ClearWrite(Lit("metahash.dat"), StringFromStruct(&new_metahash)); } else { EchoLine(Lit("Metaprogram is dirty")); ExitNow(MetaRebuildCode); } } //////////////////////////////// //~ Args //- Unpack args StringList args = GetCommandLineArgs(); for (StringListNode *n = args.first; n; n = n->next) { String arg = n->s; } //- Generate compiler flags StringList compiler_defs = ZI; StringList warning_flags_msvc = ZI; StringList warning_flags_clang = ZI; StringList flags_msvc = ZI; StringList flags_clang = ZI; StringList compiler_flags_msvc = ZI; StringList compiler_flags_clang = ZI; StringList linker_flags_msvc = ZI; StringList linker_flags_clang = ZI; { //- Shared definitions PushStringToList(arena, &compiler_defs, Lit("-DIsConsoleApp=0")); PushStringToList(arena, &compiler_defs, Lit("-DRtcIsEnabled=1")); PushStringToList(arena, &compiler_defs, Lit("-DAsanIsEnabled=0")); PushStringToList(arena, &compiler_defs, Lit("-DCrtlibIsEnabled=1")); PushStringToList(arena, &compiler_defs, Lit("-DDebinfoEnabled=1")); PushStringToList(arena, &compiler_defs, Lit("-DDeveloperIsEnabled=1")); PushStringToList(arena, &compiler_defs, Lit("-DProfilingIsEnabled=0")); PushStringToList(arena, &compiler_defs, Lit("-DUnoptimizedIsEnabled=1")); PushStringToList(arena, &compiler_defs, Lit("-DTestsAreEnabled=0")); //- Msvc { PushStringToList(arena, &compiler_flags_msvc, Lit("-diagnostics:column")); PushStringToList(arena, &flags_msvc, Lit("-INCREMENTAL:NO")); PushStringToList(arena, &flags_msvc, Lit("-nologo")); /* Debug info */ PushStringToList(arena, &flags_msvc, Lit("-DEBUG:FULL")); PushStringToList(arena, &compiler_flags_msvc, Lit("-Z7")); /* Enable warnings */ PushStringToList(arena, &warning_flags_msvc, Lit("-W4")); PushStringToList(arena, &warning_flags_msvc, Lit("-WX")); // PushStringToList(arena, &warning_flags_msvc, Lit("-we4013")); /* function undefined; assuming extern returning int */ /* Disable warnings */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4244")); /* 'function': conversion from 'int' to 'f32', possible loss of data */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4201")); /* nonstandard extension used: nameless struct/union */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4324")); /* structure was padded due to alignment specifier */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4100")); /* unreferenced parameter */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4101")); /* unreferenced local variable */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4189")); /* local variable is initialized but not referenced */ PushStringToList(arena, &warning_flags_msvc, Lit("-wd4200")); /* nonstandard extension used: zero-sized array in struct/union */ } //- Clang { PushStringToList(arena, &flags_clang, Lit("-std=c99")); PushStringToList(arena, &flags_clang, Lit("-fno-finite-loops")); PushStringToList(arena, &flags_clang, Lit("-fno-strict-aliasing")); PushStringToList(arena, &flags_clang, Lit("-g -gcodeview")); PushStringToList(arena, &flags_clang, Lit("-O0")); PushStringToList(arena, &flags_clang, Lit("-msse4.2")); /* Enable warnings */ PushStringToList(arena, &warning_flags_clang, Lit("-Wall")); PushStringToList(arena, &warning_flags_clang, Lit("-Werror")); PushStringToList(arena, &warning_flags_clang, Lit("-Wframe-larger-than=65536")); PushStringToList(arena, &warning_flags_clang, Lit("-Wmissing-prototypes")); PushStringToList(arena, &warning_flags_clang, Lit("-Wmissing-declarations")); PushStringToList(arena, &warning_flags_clang, Lit("-Wunused-variable")); PushStringToList(arena, &warning_flags_clang, Lit("-Wunused-but-set-variable")); PushStringToList(arena, &warning_flags_clang, Lit("-Wunused-parameter")); PushStringToList(arena, &warning_flags_clang, Lit("-Wimplicit-fallthrough")); PushStringToList(arena, &warning_flags_clang, Lit("-Wswitch")); /* Disable warnings */ PushStringToList(arena, &warning_flags_clang, Lit("-Wno-initializer-overrides")); PushStringToList(arena, &warning_flags_clang, Lit("-Wno-microsoft-enum-forward-reference")); } } //////////////////////////////// //~ Parse //- Lex layers M_TokenFileList lexed = ZI; { StringList src_dirs = ZI; PushStringToList(arena, &src_dirs, Lit("../src")); lexed = M_TokensFromSrcDirs(arena, src_dirs); } //- Parse layers M_LayerList parsed = ZI; { parsed = M_LayersFromTokenFiles(arena, lexed); } //- Flatten layers M_Layer flattened = ZI; { StringList starting_layer_names = ZI; PushStringToList(arena, &starting_layer_names, Lit("pp")); flattened = M_GetFlattenedEntries(arena, parsed, starting_layer_names); } M_AppendErrors(arena, &errors, flattened.errors); //- Process flattened layers StringList c_include_lines = ZI; StringList gpu_include_lines = ZI; StringList c_startup_lines = ZI; StringList c_store_lines = ZI; Struct(EmbeddedDir) { EmbeddedDir *next; String store_name; String dir_name; }; EmbeddedDir *first_dir_embed = 0; EmbeddedDir *last_dir_embed = 0; u64 num_dir_embeds = 0; if (errors.count <= 0) { for (M_Entry *entry = flattened.first; entry->valid; entry = entry->next) { M_EntryKind kind = entry->kind; M_Token *entry_tok = entry->name_token; M_Token *arg0_tok = entry->arg_tokens[0]; M_Token *arg1_tok = entry->arg_tokens[1]; switch(kind) { case M_EntryKind_Unknown: { M_PushError(arena, &errors, entry_tok, StringF(arena, "Unknown entry kind \"%F\"", FmtString(entry_tok->s))); } break; case M_EntryKind_IncludeC: case M_EntryKind_IncludeGpu: { if (arg0_tok->valid) { String token_file = arg0_tok->file->name; String token_parent_dir = F_GetParentDir(token_file); String arg_file = arg0_tok->s; String full = F_GetFull(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_file))); if (F_IsFile(full)) { String line = StringF(arena, "#include \"%F\"", FmtString(full)); if (kind == M_EntryKind_IncludeC) { PushStringToList(arena, &c_include_lines, line); } else { PushStringToList(arena, &gpu_include_lines, line); } } else { String err = StringF(arena, "File '%F' not found", FmtString(full)); M_PushError(arena, &errors, arg0_tok, err); } } else { M_PushError(arena, &errors, entry_tok, Lit("Expected file name")); } } break; case M_EntryKind_Startup: { if (arg0_tok->valid) { String startup = arg0_tok->s; String line = StringF(arena, " %F();", FmtString(startup)); PushStringToList(arena, &c_startup_lines, line); } else { M_PushError(arena, &errors, entry_tok, Lit("Expected startup function name")); } } break; case M_EntryKind_EmbedDir: { if (arg0_tok->valid && arg1_tok->valid) { String store_name = arg0_tok->s; String token_file = arg1_tok->file->name; String token_parent_dir = F_GetParentDir(token_file); String arg_dir = arg1_tok->s; String full = F_GetFullCrossPlatform(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_dir))); if (F_IsDir(full)) { u64 hash = HashFnv64(Fnv64Basis, StringF(arena, "%F/", FmtString(store_name))); String line = StringF(arena, "ResourceStore %F = { 0x%F };", FmtString(store_name), FmtHex(hash)); PushStringToList(arena, &c_store_lines, line); EmbeddedDir *ed = PushStruct(arena, EmbeddedDir); ed->store_name = store_name; ed->dir_name = full; QueuePush(first_dir_embed, last_dir_embed, ed); ++num_dir_embeds; } else { String err = StringF(arena, "Directory '%F' not found", FmtString(full)); M_PushError(arena, &errors, arg1_tok, err); } } else { M_PushError(arena, &errors, entry_tok, Lit("Expected resource store & directory name")); } } break; } } } //- Echo meta errors if (ret == 0) { ret = errors.count > 0; } for (M_Error *e = errors.first; e; e = e->next) { String msg = ZI; M_Token *token = e->token; String token_file = token->file->name; String token_file_data = token->file->data; if (token_file.len > 0) { i64 token_pos = -1; if (token->s.len > 0 && token_file_data.len > 0 && token->s.text > token_file_data.text && token->s.text < (token_file_data.text + token_file_data.len)) { token_pos = token->s.text - token_file_data.text; } LineCol line_col = ZI; if (token_pos >= 0) { line_col = LineColFromPos(token_file_data, token_pos); } msg = StringF(arena, "%F:%F:%F: error: %F", FmtString(token_file), FmtSint(line_col.line), FmtSint(line_col.col), FmtString(e->msg)); } else { msg = StringF(arena, "error: %F", FmtString(e->msg)); } EchoLine(msg); } //////////////////////////////// //~ Generate //- Generate C file String c_out_file = F_GetFull(arena, Lit("pp_gen.c")); if (errors.count <= 0) { StringList c_out_lines = ZI; PushStringToList(arena, &c_out_lines, Lit("// Auto generated file")); /* Include ase layer */ { String base_inc_path = F_GetFull(arena, Lit("../src/base/base_inc.h")); PushStringToList(arena, &c_out_lines, Lit("")); PushStringToList(arena, &c_out_lines, Lit("//- Base layer includes")); String line = StringF(arena, "#include \"%F\"", FmtString(base_inc_path)); PushStringToList(arena, &c_out_lines, line); } /* Include dependency layers */ if (c_out_lines.count > 0) { PushStringToList(arena, &c_out_lines, Lit("")); PushStringToList(arena, &c_out_lines, Lit("//- Dependency includes")); for (StringListNode *n = c_include_lines.first; n; n = n->next) { PushStringToList(arena, &c_out_lines, n->s); } } /* Define resource stores */ if (c_store_lines.count > 0) { PushStringToList(arena, &c_out_lines, Lit("")); PushStringToList(arena, &c_out_lines, Lit("//- Resource stores")); for (StringListNode *n = c_store_lines.first; n; n = n->next) { PushStringToList(arena, &c_out_lines, n->s); } } /* Define StartupLayers */ { PushStringToList(arena, &c_out_lines, Lit("")); PushStringToList(arena, &c_out_lines, Lit("//- Startup")); PushStringToList(arena, &c_out_lines, Lit("void StartupLayers(void)")); PushStringToList(arena, &c_out_lines, Lit("{")); for (StringListNode *n = c_startup_lines.first; n; n = n->next) { PushStringToList(arena, &c_out_lines, n->s); } PushStringToList(arena, &c_out_lines, Lit("}")); PushStringToList(arena, &c_out_lines, Lit("")); } /* Write to file */ String c_out = StringFromList(arena, c_out_lines, Lit("\n")); F_ClearWrite(c_out_file, c_out); } //- Generate archive file String arc_out_file = F_GetFull(arena, Lit("resources.arc")); if (ret == 0) { EchoLine(Lit("[Creating archive]")); Struct(EntryNode) { EntryNode *next; String entry_name; String file_name; }; EntryNode *first_entry = 0; EntryNode *last_entry = 0; u64 entries_count = 0; for (EmbeddedDir *ed = first_dir_embed; ed; ed = ed->next) { String store_name = ed->store_name; String embed_dir = ed->dir_name; StringList files = ZI; F_FilesFromDir(arena, &files, embed_dir, F_IterFlag_Recurse); for (StringListNode *file_node = files.first; file_node; file_node = file_node->next) { String file_name = file_node->s; if (F_IsFile(file_name)) { String entry_name = file_name; if (entry_name.len > (embed_dir.len + 1)) { entry_name.len -= embed_dir.len + 1; entry_name.text += embed_dir.len + 1; } entry_name = StringF(arena, "%F/%F", FmtString(store_name), FmtString(entry_name)); for (u64 i = 0; i < entry_name.len; ++i) { if (entry_name.text[i] == '\\') { entry_name.text[i] = '/'; } } EntryNode *en = PushStruct(arena, EntryNode); en->entry_name = entry_name; en->file_name = file_name; QueuePush(first_entry, last_entry, en); ++entries_count; } } } BB_Buff bb = BB_AcquireBuff(Gibi(64)); BB_Writer bw = BB_WriterFromBuff(&bb); /* Write magic */ BB_WriteUBits(&bw, ResourceEmbeddedMagic, 64); /* Write header */ BB_WriteUBits(&bw, entries_count, 64); /* Reserve entries space */ u64 entry_size = 8 /* Name start */ + 8 /* Name end */ + 8 /* Data start */ + 8; /* Data end */ u8 *entries_start = BB_GetWrittenRaw(&bw) + BB_GetNumBytesWritten(&bw); u64 entries_size = entry_size * entries_count; String entries_str = STRING(entries_size, entries_start); BB_WriteSeekBytes(&bw, entries_size); BB_Buff entries_bb = BB_BuffFromString(entries_str); BB_Writer entries_bw = BB_WriterFromBuff(&entries_bb); /* Write entries */ for (EntryNode *en = first_entry; en; en = en->next) { /* TODO: Copy file data directly into archive file */ String file_data = F_DataFromFile(arena, en->file_name); /* Write name */ BB_WriteAlignByte(&bw, 64); u64 name_start = BB_GetNumBytesWritten(&bw) + 1; BB_WriteString(&bw, en->entry_name); u64 name_len = BB_GetNumBytesWritten(&bw) - name_start; /* Write data */ BB_WriteAlignByte(&bw, 64); /* FIXME: Why no +1 here? */ u64 data_start = BB_GetNumBytesWritten(&bw); BB_WriteBytes(&bw, file_data); u64 data_len = BB_GetNumBytesWritten(&bw) - data_start; /* Write entry */ BB_WriteUBits(&entries_bw, name_start, 64); BB_WriteUBits(&entries_bw, name_len, 64); BB_WriteUBits(&entries_bw, data_start, 64); BB_WriteUBits(&entries_bw, data_len, 64); } /* Write to file */ String arc_out = ZI; arc_out.len = BB_GetNumBytesWritten(&bw); arc_out.text = BB_GetWrittenRaw(&bw); F_ClearWrite(arc_out_file, arc_out); EchoLine(StringF(arena, "Built %F (%F mb)", FmtString(F_GetFileName(arc_out_file)), FmtFloatP((f32)arc_out.len / 1024 / 1024, 3))); } //- Generate rc file String rc_out_file = F_GetFull(arena, Lit("resources.rc")); { StringList rc_out_lines = ZI; String arc_file_cp = F_GetFullCrossPlatform(arena, arc_out_file); String line = StringF(arena, "%F RCDATA \"%F\"", FmtString(Lit(Stringize(W32_EmbeddedDataName))), FmtString(arc_file_cp)); PushStringToList(arena, &rc_out_lines, line); /* Write to file */ String rc_out = StringFromList(arena, rc_out_lines, Lit("\n")); F_ClearWrite(rc_out_file, rc_out); } //////////////////////////////// //~ Compile //- Compile RC String res_out_file = F_GetFull(arena, Lit("resources.res")); if (ret == 0) { EchoLine(Lit("[Compiling RC]")); String cmd = StringF(arena, "cmd /c rc.exe -nologo -fo %F %F", FmtString(res_out_file), FmtString(rc_out_file)); OS_CommandResult result = OS_RunCommand(arena, cmd); String output = TrimWhitespace(result.output); EchoLine(F_GetFileName(rc_out_file)); if (output.len > 0) { EchoLine(output); } ret = result.code; } //- Compile C String c_out_obj_file = Lit("pp_gen.obj"); if (ret == 0) { EchoLine(Lit("[Compiling C]")); String cmd = StringF(arena, "cmd /c cl.exe /c %F -Fo:%F %F %F %F %F", FmtString(c_out_file), FmtString(c_out_obj_file), FmtString(StringFromList(arena, flags_msvc, Lit(" "))), FmtString(StringFromList(arena, compiler_flags_msvc, Lit(" "))), FmtString(StringFromList(arena, warning_flags_msvc, Lit(" "))), FmtString(StringFromList(arena, compiler_defs, Lit(" ")))); OS_CommandResult result = OS_RunCommand(arena, cmd); String output = TrimWhitespace(result.output); EchoLine(output); ret = result.code; } //////////////////////////////// //~ Link //- Link String exe_file = Lit("pp.exe"); if (ret == 0) { EchoLine(Lit("[Linking]")); String cmd = StringF(arena, "cmd /c link.exe %F %F /OUT:%F %F %F", FmtString(c_out_obj_file), FmtString(res_out_file), FmtString(exe_file), FmtString(StringFromList(arena, flags_msvc, Lit(" "))), FmtString(StringFromList(arena, linker_flags_msvc, Lit(" ")))); OS_CommandResult result = OS_RunCommand(arena, cmd); String output = TrimWhitespace(result.output); if (output.len > 0) { EchoLine(output); } ret = result.code; } #if 0 //- Generate compiler cmds String msvc_cmd_str = ZI; String clang_cmd_str = ZI; /* Msvc */ { String compiler_str = StringF(arena, "%F %F %F %F", FmtString(StringFromList(arena, flags_msvc, Lit(" "))), FmtString(StringFromList(arena, compiler_flags_msvc, Lit(" "))), FmtString(StringFromList(arena, warning_flags_msvc, Lit(" "))), FmtString(StringFromList(arena, compiler_defs, Lit(" ")))); String linker_str = StringF(arena, "%F %F", FmtString(StringFromList(arena, flags_msvc, Lit(" "))), FmtString(StringFromList(arena, linker_flags_msvc, Lit(" ")))); msvc_cmd_str = StringF(arena, "\"cl\" %F %F -link %F", FmtString(c_out_file), FmtString(compiler_str), FmtString(linker_str)); } /* Clang */ { String compiler_str = StringF(arena, "%F %F %F %F", FmtString(StringFromList(arena, flags_clang, Lit(" "))), FmtString(StringFromList(arena, compiler_flags_clang, Lit(" "))), FmtString(StringFromList(arena, warning_flags_clang, Lit(" "))), FmtString(StringFromList(arena, compiler_defs, Lit(" ")))); clang_cmd_str = StringF(arena, "\"clang\" %F %F", FmtString(c_out_file), FmtString(compiler_str)); } //- Compile C if (ret == 0) { EchoLine(Lit("Compiling")); String cmd_str = msvc_cmd_str; // String cmd_str = clang_cmd_str; OS_CommandResult result = OS_RunCommand(arena, cmd_str); String output = result.output; output = Trim(output, Lit("\n")); output = Trim(output, Lit("\r")); output = Trim(output, Lit("\n")); EchoLine(output); if (result.code == 0) { EchoLine(Lit("Compilation succeeded")); } else { EchoLine(Lit("Compilation failed")); } ret = result.code; } #endif ExitNow(ret); } //////////////////////////////// //~ @hookdef Startup void StartupLayers(void) { OS_Startup(); StartupMeta(); }