diff options
-rwxr-xr-x | saw.c | 549 |
1 files changed, 328 insertions, 221 deletions
@@ -15,14 +15,39 @@ #/ - No configuration required #/ - Cross-platform thanks to sokol and miniaudio #/ -#/ NOTE -#/ You can build the project easily using this two commands: -#/ > gcc -o buildme -DBUILDME saw.c +#/ ---------------------------------------------------------------- +#/ +#/ NOTE Automatic self-compilation +#/ +#/ If you have a Linux shell, you can build the project by running +#/ > ./saw.c +#/ +#/ Or you can build the project easily using those two commands: +#/ > gcc -o buildme -DBUILDME=1 saw.c #/ > ./buildme #/ Or, on Windows, with MSVC compiler: -#/ > cl.exe -Febuildme.exe -DBUILDME saw.c +#/ > cl.exe -Febuildme.exe -DBUILDME=1 saw.c #/ > buildme.exe #/ +#/ NOTE Manual compilation +#/ +#/ - Code generation +#/ > gcc -o build_codegen -DCODEGEN=1 saw.c +#/ > ./build_codegen +#/ +#/ - Dependencies +#/ > gcc -c -o build/thirdparty.o -DDEPENDENCIES=1 saw.c +#/ +#/ - Test suite +#/ > gcc -o build/test_suite -DTESTS=1 +#/ > build/test_suite +#/ +#/ - Executable +#/ > gcc -o build/saw -DEXE=1 +#/ > build/saw +#/ +#/ ---------------------------------------------------------------- +#/ #/ To-Do list #/ #/ - Code @@ -143,11 +168,12 @@ exit $? # */ // // ================================================================ // Define one of the options for the linter -#if !BUILDME && \ +#if !CODEGEN && \ + !BUILDME && \ !DEPENDENCIES && \ !EXE && \ !TESTS -#define EXE 1 +#define BUILDME 1 #endif #define _GNU_SOURCE // ================================================================ @@ -156,19 +182,153 @@ exit $? # */ // // ================================================================ -#if BUILDME +#if CODEGEN || BUILDME -typedef int i32; -typedef long long i64; -typedef unsigned u32; -typedef char c8; -typedef signed char b8; +enum { + LINUX, + WINDOWS, + MACOS, +}; + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define DLM "\\" +#define OS WINDOWS +#elif defined(__APPLE__) +#define DLM "/" +#define OS MACOS +#else // assume Linux +#define DLM "/" +#define OS LINUX +#endif + +#define FONT_TEXT "fonts" DLM "domitian_roman.ttf" +#define FONT_ICONS "fonts" DLM "font_awesome_6_free_solid_900.ttf" +#define INL_FONTS "build_fonts.inl.c" +#define SOURCE "saw.c" +#define PROJECT "saw" + +typedef int i32; +typedef long long i64; +typedef unsigned u32; +typedef unsigned long long u64; +typedef char c8; +typedef signed char b8; #include <stdio.h> #include <stdlib.h> + +enum { + MAX_LENGTH = 200, +}; + +i64 int_len(u32 x) { + i64 len = 0; + + do { + x /= 10; + ++len; + } while (x > 0); + + return len; +} + +i64 print_bytes(FILE *out, FILE *in) { + i64 size = 0, line_len = MAX_LENGTH; + + while (!feof(in)) { + u32 x = 0; + + i64 n = fread(&x, 1, sizeof x, in); + if (n <= 0) + break; + + i64 len = int_len(x); + + line_len += len + 2; + + if (line_len >= MAX_LENGTH) { + fprintf(out, "\n "); + line_len = 3 + len; + } + + fprintf(out, " %u,", x); + + size += n; + } + + return size; +} + +void codegen() { + printf("Code generation\n"); + fflush(stdout); + + FILE *out = fopen(INL_FONTS, "wb"); + + if (out == NULL) { + printf("Unable to write `%s`", INL_FONTS); + exit(-1); + } + + fprintf(out, "// Saw generated code\n\n"); + fprintf(out, "#include \"kit/types.h\"\n\n"); + + { + FILE *in = fopen(FONT_TEXT, "rb"); + + if (in == NULL) { + printf("Unable to read `%s`", FONT_TEXT); + exit(-1); + } + + fprintf(out, "u32 ttf_text[] = {"); + + i64 n = print_bytes(out, in); + + fprintf(out, "\n};\n\n"); + fprintf(out, "enum { TTF_TEXT_SIZE = %lld, };\n\n", n); + + fclose(in); + } + + { + FILE *in = fopen(FONT_ICONS, "rb"); + + if (in == NULL) { + printf("Unable to read `%s`", FONT_ICONS); + exit(-1); + } + + + fprintf(out, "u32 ttf_icons[] = {"); + + i64 n = print_bytes(out, in); + + fprintf(out, "\n};\n\n"); + fprintf(out, "enum { TTF_ICONS_SIZE = %lld, };\n\n", n); + + fclose(in); + } + + fclose(out); +} + +#endif + +#if CODEGEN + +int main(int argc, char **argv) { + (void) argc; + (void) argv; + + codegen(); + return 0; +} + +#elif BUILDME + #include <string.h> #include <stdarg.h> -#include <assert.h> #if defined(_WIN32) && !defined(__CYGWIN__) # define WIN32_LEAN_AND_MEAN @@ -178,9 +338,6 @@ typedef signed char b8; # include <sys/stat.h> #endif -#define PROJECT "saw" -#define SOURCE "saw.c" - #define REQUIRE_MATH 1 #define REQUIRE_DL 0 #define REQUIRE_THREADS 0 @@ -197,23 +354,6 @@ typedef signed char b8; // TODO Add command line option for the C runtime. enum { - LINUX, - WINDOWS, - MACOS, -}; - -#if defined(_WIN32) && !defined(__CYGWIN__) -#define DLM "\\" -#define OS WINDOWS -#elif defined(__APPLE__) -#define DLM "/" -#define OS MACOS -#else // assume Linux -#define DLM "/" -#define OS LINUX -#endif - -enum { BUFFER_COUNT = 16, BUFFER_SIZE = 512, STRING_COUNT = 64, @@ -238,7 +378,11 @@ c8 *flag_exe = "-o "; c8 *flags = ""; c8 *link_flags = ""; -b8 run_tests = 1; +b8 rebuild_all = 0; +b8 rebuild_exe = 0; +b8 run_codegen = 0; +b8 run_tests = 1; +b8 run_exe = 0; void print_help(void) { printf( @@ -250,7 +394,11 @@ void print_help(void) { " -d --destination - Set destination path\n" " -o --options - Set additional compiler options\n" " -l --link - Set additional linker options\n" - " -s --skiptests - Do not run tests\n\n" + " -e --rebuild - Rebuild executable\n" + " -r --rebuildall - Rebuild dependencies\n" + " -g --codegen - Run code generation\n" + " -s --skiptests - Do not run tests\n" + " -x --run - Run the executable\n\n" ); fflush(stdout); } @@ -264,7 +412,10 @@ c8 lowercase_char(c8 c) { c8 *lowercase(c8 *s) { i32 i = 0; for (; s[i] != '\0'; ++i) { - assert(i + 1 < BUFFER_SIZE); + if (i + 1 >= BUFFER_SIZE) { + printf("Buffer overflow\n"); + exit(-1); + } _buffers[_buffer_index][i] = lowercase_char(s[i]); } _buffers[_buffer_index][i] = '\0'; @@ -294,7 +445,10 @@ void fmt_dup_v(c8 *format, va_list args) { c8 *fmt_dup(c8 *format, ...) { va_list args; va_start(args, format); - assert(_string_index < STRING_COUNT); + if (_string_index >= STRING_COUNT) { + printf("Buffer overflow\n"); + exit(-1); + } fmt_v(format, args); c8 *result = _strings[_string_index++]; va_end(args); @@ -334,6 +488,52 @@ b8 file_exists(c8 *name) { return 0; } +typedef struct { + u64 seconds; + u32 nanoseconds; +} Timestamp; + +Timestamp file_timestamp(c8 *name) { + Timestamp time = { + .seconds = 0, + .nanoseconds = 0, + }; + +#if defined(_WIN32) && !defined(__CYGWIN__) + HANDLE f = CreateFileA(buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + FILETIME ft; + if (f != INVALID_HANDLE_VALUE && GetFileTime(f, NULL, NULL, &ft) != 0) { + u64 nsec100 = (((u64) ft.dwHighDateTime) << 32) | (u64) ft.dwLowDateTime; + time = (Timestamp) { + .seconds = (u64) (nsec100 / 10000000), + .nanoseconds = (u32) (100 * (nsec100 % 10000000)), + }; + } + if (f != INVALID_HANDLE_VALUE) + CloseHandle(f); +#else + struct stat info; + if (stat(name, &info) == 0 && (S_ISREG(info.st_mode) || S_ISDIR(info.st_mode))) + time = (Timestamp) { + #ifdef st_mtime + .seconds = (u64) info.st_mtim.tv_sec, + .nanoseconds = (u32) info.st_mtim.tv_nsec, + #else + .seconds = (u64) info.st_mtime, + .nanoseconds = 0, + #endif + }; +#endif + + return time; +} + +b8 time_less(Timestamp a, Timestamp b) { + if (a.seconds == b.seconds) + return a.nanoseconds < b.nanoseconds; + return a.seconds < b.seconds; +} + void create_folder_recursive(c8 *path) { #if defined(_WIN32) && !defined(__CYGWIN__) system(fmt("if not exist %s mkdir %s", destination, destination)); @@ -342,48 +542,6 @@ void create_folder_recursive(c8 *path) { #endif } -enum { - MAX_LENGTH = 200, -}; - -i64 int_len(u32 x) { - i64 len = 0; - - do { - x /= 10; - ++len; - } while (x > 0); - - return len; -} - -i64 print_bytes(FILE *out, FILE *in) { - i64 size = 0, line_len = MAX_LENGTH; - - while (!feof(in)) { - u32 x = 0; - - i64 n = fread(&x, 1, sizeof x, in); - if (n <= 0) - break; - - i64 len = int_len(x); - - line_len += len + 2; - - if (line_len >= MAX_LENGTH) { - fprintf(out, "\n "); - line_len = 3 + len; - } - - fprintf(out, " %u,", x); - - size += n; - } - - return size; -} - i32 main(i32 argc, c8 **argv) { // Handle command line arguments // @@ -409,8 +567,16 @@ i32 main(i32 argc, c8 **argv) { extra_options = argv[++i]; else if (str_eq_lower(opt, "link")) extra_link_options = argv[++i]; + else if (str_eq_lower(opt, "rebuild")) + rebuild_exe = 1; + else if (str_eq_lower(opt, "rebuildall")) + rebuild_all = 1; + else if (str_eq_lower(opt, "codegen")) + run_codegen = 1; else if (str_eq_lower(opt, "skiptests")) run_tests = 0; + else if (str_eq_lower(opt, "run")) + run_exe = 1; else printf("Unknown option ignored `%s`\n", argv[i]); } else { @@ -422,29 +588,16 @@ i32 main(i32 argc, c8 **argv) { print_help(); return 0; - case 't': - build_type = argv[i + (++consumed)]; - break; - - case 'c': - compiler_c = argv[i + (++consumed)]; - break; - - case 'd': - destination = argv[i + (++consumed)]; - break; - - case 'o': - extra_options = argv[i + (++consumed)]; - break; - - case 'l': - extra_link_options = argv[i + (++consumed)]; - break; - - case 's': - run_tests = 0; - break; + case 't': build_type = argv[i + (++consumed)]; break; + case 'c': compiler_c = argv[i + (++consumed)]; break; + case 'd': destination = argv[i + (++consumed)]; break; + case 'o': extra_options = argv[i + (++consumed)]; break; + case 'l': extra_link_options = argv[i + (++consumed)]; break; + case 'e': rebuild_exe = 1; break; + case 'r': rebuild_all = 1; break; + case 'g': run_codegen = 1; break; + case 's': run_tests = 0; break; + case 'x': run_exe = 1; break; default: printf("Unknown option ignored `-%c`\n", argv[i][j]); @@ -452,9 +605,8 @@ i32 main(i32 argc, c8 **argv) { } i += consumed; } - } else { + } else printf("Unknown option ignored `%s`\n", argv[i]); - } } fflush(stdout); @@ -629,9 +781,9 @@ i32 main(i32 argc, c8 **argv) { ""; if (extra_options[0] != '\0') - flags = fmt_dup("%s %s", extra_options, flags); + flags = fmt_dup("%s %s", flags, extra_options); if (extra_link_options[0] != '\0') - link_flags = fmt_dup("%s %s", extra_link_options, link_flags); + link_flags = fmt_dup("%s %s", link_flags, extra_link_options); // Prepare destination folder // @@ -639,82 +791,43 @@ i32 main(i32 argc, c8 **argv) { destination[0] == '\0' && (destination = "build"); create_folder_recursive(destination); - // Print info - // - - printf("\nCompiler options: %s\n", flags); - printf( "Link options: %s\n\n", link_flags); - fflush(stdout); - - // Code generation + // Check timestamps // - // TODO Add command line option for code generation. - - if (!file_exists("build_fonts.inl.h")) { - printf("Code generation\n"); - fflush(stdout); - - FILE *out = fopen("build_fonts.inl.h", "wb"); - assert(out != NULL); - - fprintf(out, "// " - "=====================================================" - "===========\n"); - fprintf(out, "//\n"); - fprintf(out, "// Saw generated code\n"); - fprintf(out, "//\n"); - fprintf(out, "// " - "=====================================================" - "===========\n\n"); - - fprintf(out, "#ifndef BUILD_FONTS_INL_H\n"); - fprintf(out, "#define BUILD_FONTS_INL_H\n\n"); - fprintf(out, "#include \"kit/types.h\"\n\n"); - - // Write Domitian Roman - // - { - FILE *in = fopen("fonts/domitian_roman.ttf", "rb"); - assert(in != NULL); - fprintf(out, "u32 ttf_text[] = {"); + Timestamp time_font_text = file_timestamp(FONT_TEXT); + Timestamp time_font_icons = file_timestamp(FONT_ICONS); + Timestamp time_fonts = file_timestamp(INL_FONTS); + Timestamp time_source = file_timestamp(SOURCE); + Timestamp time_tests = file_timestamp(fmt("%s" DLM "test_suite", destination)); + Timestamp time_exe = file_timestamp(fmt("%s" DLM PROJECT, destination)); - i64 n = print_bytes(out, in); + if (time_less(time_fonts, time_font_text) || + time_less(time_fonts, time_font_icons)) + run_codegen = 1; - fprintf(out, "\n};\n\n"); - fprintf(out, "enum { TTF_TEXT_SIZE = %lld, };\n\n", n); - - fclose(in); - } - - // Write Font Awesome - // - { - FILE *in = fopen("fonts/font_awesome_6_free_solid_900.ttf", "rb"); - assert(in != NULL); - - fprintf(out, "u32 ttf_icons[] = {"); - - i64 n = print_bytes(out, in); + if (!file_exists(fmt("%s" DLM "thirdparty%s", destination, postfix_obj))) { + rebuild_all = 1; + rebuild_exe = 1; + } - fprintf(out, "\n};\n\n"); - fprintf(out, "enum { TTF_ICONS_SIZE = %lld, };\n\n", n); + if (time_less(time_tests, time_source) || + time_less(time_exe, time_source)) + rebuild_exe = 1; - fclose(in); - } + // Print info + // - fprintf(out, "#endif\n"); - fclose(out); - } + printf("\nCompiler options: %s\n", flags); + printf( "Link options: %s\n\n", link_flags); + fflush(stdout); // Build the project // - c8 *deps = fmt("%s" DLM "thirdparty%s", destination, postfix_obj); - - if (!file_exists(deps)) { - // TODO Add command line option for complete rebuild. + if (run_codegen) + codegen(); + if (rebuild_all) { printf("Rebuild dependencies\n"); fflush(stdout); @@ -733,65 +846,59 @@ i32 main(i32 argc, c8 **argv) { fflush(stdout); } - printf("Build the test suite\n"); - fflush(stdout); - - i32 s = system(fmt( - "%s %s -DTESTS=1 " - "%s%s" DLM "test_suite%s " - SOURCE - " %s", - compiler_c, flags, - flag_exe, destination, postfix_exe, - link_flags) - ); - - if (WEXITSTATUS(s) != 0) - return 1; - fflush(stdout); - - printf("Build " PROJECT " executable\n"); - fflush(stdout); + if (rebuild_exe) { + printf("Build the test suite\n"); + fflush(stdout); - s = system(fmt( - "%s %s -DEXE=1 " - "%s%s" DLM PROJECT "%s " - "%s" DLM "thirdparty%s " - SOURCE - " %s", - compiler_c, flags, - flag_exe, destination, postfix_exe, - destination, postfix_obj, - link_flags) - ); + i32 s = system(fmt( + "%s %s -DTESTS=1 " + "%s%s" DLM "test_suite%s " + SOURCE + " %s", + compiler_c, flags, + flag_exe, destination, postfix_exe, + link_flags) + ); - if (WEXITSTATUS(s) != 0) - return 1; - fflush(stdout); + if (WEXITSTATUS(s) != 0) + return 1; + fflush(stdout); - if (!run_tests) - return 0; + printf("Build " PROJECT " executable\n"); + fflush(stdout); - // Run tests - // + s = system(fmt( + "%s %s -DEXE=1 " + "%s%s" DLM PROJECT "%s " + "%s" DLM "thirdparty%s " + SOURCE + " %s", + compiler_c, flags, + flag_exe, destination, postfix_exe, + destination, postfix_obj, + link_flags) + ); - i32 status = 0; + if (WEXITSTATUS(s) != 0) + return 1; + fflush(stdout); + } - printf("Run tests\n\n"); - fflush(stdout); + if (run_tests) { + printf("Run tests\n"); + fflush(stdout); - s = system(fmt("\"%s" DLM "test_suite\"", destination)); + i32 s = system(fmt("\"%s" DLM "test_suite\"", destination)); - if (WEXITSTATUS(s) != 0) - status = 1; - fflush(stdout); + if (WEXITSTATUS(s) != 0) + return -1; + } - if (status == 0) - printf("\nAll done - OK.\n"); - else - printf("\nAll done - FAILURE.\n"); + if (run_exe) + system(fmt("\"%s" DLM PROJECT "\"", destination)); - return status; + printf("All done\n"); + return 0; } // ================================================================ @@ -882,7 +989,7 @@ i32 main(i32 argc, c8 **argv) { // ttf_text, TTF_TEXT_SIZE // ttf_icons, TTF_ICONS_SIZE -#include "build_fonts.inl.h" +#include "build_fonts.inl.c" // ================================================================ // @@ -4724,5 +4831,5 @@ i32 main(i32 argc, char **argv) { // ================================================================ #else -#error Build options not provided. Try: gcc -DBUILDME saw.c +#error Build options not provided. Try: gcc -DBUILDME=1 saw.c #endif |