#if 0 SRC=${0##*/} BIN=${SRC%.*} gcc -o $BIN.tmp $SRC && ./$BIN.tmp $@ && rm $BIN.tmp exit $? #endif #include #include #include #include #include #if defined(_WIN32) && !defined(__CYGWIN__) # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN 1 # endif # ifndef NOMINMAX # define NOMINMAX # endif # include #endif #define PROJECT "kit" #define SOURCE_LIB "source/kit/_lib.c" #define FOLDER_TESTS "source/tests/" #define REQUIRE_MATH 0 #define REQUIRE_DL 0 #define REQUIRE_THREADS 1 #define REQUIRE_SOCKETS 1 #define REQUIRE_GRAPHICS 0 #define REQUIRE_OPENGL 0 #define REQUIRE_VULKAN 0 #define STATIC_RUNTIME 0 typedef int i32; typedef char c8; typedef signed char b8; enum { LINUX, WINDOWS, MACOS, }; #if defined(_WIN32) && !defined(__CYGWIN__) #define DLM "\\" enum { OS = WINDOWS, }; #elif defined(__APPLE__) #define DLM "/" enum { OS = MACOS, }; #else #define DLM "/" enum { OS = LINUX, }; #endif enum { BUFFER_COUNT = 16, BUFFER_SIZE = 512, STRING_COUNT = 64, STRING_SIZE = 512, }; c8 _buffers[BUFFER_COUNT][BUFFER_SIZE]; i32 _buffer_index = 0; c8 _strings[STRING_COUNT][STRING_SIZE]; i32 _string_index; c8 const *build_type = "debug"; c8 const *compiler_c = ""; c8 const *compiler_cpp = ""; c8 const *destination = ""; c8 const *extra_options = ""; c8 const *extra_link_options = ""; c8 const *postfix_obj = ".o"; c8 const *postfix_exe = ""; c8 const *flag_obj = "-c -o "; c8 const *flag_exe = "-o "; c8 const *flags = ""; c8 const *link_flags = ""; b8 run_tests = 1; void print_help() { printf( "Build tool for C projects\n\n" "Usage: ./build.c [OPTIONS]\n\n" " -h --help - Print this help\n" " -t --type - Set build type: debug, release\n" " -c --compiler - Set compiler to use: gcc, clang, cl, emcc\n" " -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" ); fflush(stdout); } c8 lowercase_char(c8 c) { if (c >= 'A' && c <= 'Z') return c + ('a' - 'A'); return c; } c8 *lowercase(c8 const *s) { i32 i = 0; for (; s[i] != '\0'; ++i) { assert(i + 1 < BUFFER_SIZE); _buffers[_buffer_index][i] = lowercase_char(s[i]); } _buffers[_buffer_index][i] = '\0'; c8 *result = _buffers[_buffer_index]; _buffer_index = (_buffer_index + 1) % BUFFER_COUNT; return result; } void fmt_v(c8 const *format, va_list args) { vsprintf(_buffers[_buffer_index], format, args); } c8 *fmt(c8 const *format, ...) { va_list args; va_start(args, format); fmt_v(format, args); c8 *result = _buffers[_buffer_index]; _buffer_index = (_buffer_index + 1) % BUFFER_COUNT; va_end(args); return result; } void fmt_dup_v(c8 const *format, va_list args) { vsprintf(_strings[_string_index], format, args); } c8 *fmt_dup(c8 const *format, ...) { va_list args; va_start(args, format); assert(_string_index < STRING_COUNT); fmt_v(format, args); c8 *result = _strings[_string_index++]; va_end(args); return result; } b8 str_eq_lower(c8 const *a, c8 const *b) { return strcmp(lowercase(a), lowercase(b)) == 0; } b8 str_eq(c8 const *a, c8 const *b) { return strcmp(a, b) == 0; } b8 check_compiler(c8 const *name) { if (str_eq_lower(name, "cl") || str_eq_lower(name, "cl.exe")) { if (system(fmt("%s", name)) != 0) return 0; return 1; } if (system(fmt("%s --version", name)) != 0) return 0; return 1; } i32 main(i32 argc, c8 **argv) { // Handle command line arguments // for (i32 i = 1; i < argc; ++i) { if (str_eq_lower(argv[i], "?")) { print_help(); return 0; } else if (argv[i][0] == '-') { if (argv[i][1] == '-') { c8 *opt = argv[i] + 2; if (str_eq_lower(opt, "help")) { print_help(); return 0; } else if (str_eq_lower(opt, "type")) build_type = argv[++i]; else if (str_eq_lower(opt, "compiler")) compiler_c = argv[++i]; else if (str_eq_lower(opt, "destination")) destination = argv[++i]; else if (str_eq_lower(opt, "options")) extra_options = argv[++i]; else if (str_eq_lower(opt, "link")) extra_link_options = argv[++i]; else if (str_eq_lower(opt, "skiptests")) run_tests = 0; else printf("Unknown option ignored `%s`\n", argv[i]); } else { i32 consumed = 0; for (i32 j = 1; argv[i][j] != '\0'; ++j) { switch (argv[i][j]) { case 'h': case 'H': 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; default: printf("Unknown option ignored `-%c`\n", argv[i][j]); } } i += consumed; } } else { printf("Unknown option ignored `%s`\n", argv[i]); } } fflush(stdout); // Find C compiler // if (compiler_c[0] != '\0') { if (check_compiler(compiler_c)) printf("C compiler found - %s\n", compiler_c); else { printf("C compiler not found\n"); return 1; } } else switch (OS) { case LINUX: if (check_compiler("gcc")) { compiler_c = "gcc"; printf("C compiler found - GCC\n"); } else if (check_compiler("clang")) { compiler_c = "clang"; printf("C compiler found - Clang"); } else if (check_compiler("cc")) { compiler_c = "cc"; printf("C compiler found - cc"); } break; case WINDOWS: if (check_compiler("cl")) { compiler_c = "cl"; printf("C compiler found - MSVC\n"); } else if (check_compiler("gcc")) { compiler_c = "gcc"; printf("C compiler found - GCC\n"); } else if (check_compiler("clang")) { compiler_c = "clang"; printf("C compiler found - Clang"); } break; case MACOS: if (check_compiler("clang")) { compiler_c = "clang"; printf("C compiler found - Clang"); } else if (check_compiler("gcc")) { compiler_c = "gcc"; printf("C compiler found - GCC\n"); } else if (check_compiler("cc")) { compiler_c = "cc"; printf("C compiler found - cc"); } break; default:; } if (compiler_c[0] == '\0') { printf("C compiler not found\n"); return 1; } fflush(stdout); // Prepare compilation options // if (OS == WINDOWS) postfix_exe = ".exe"; if (str_eq(compiler_c, "cc")) { destination[0] == '\0' && (destination = "out_cc"); compiler_cpp = "c++"; } else if (str_eq(compiler_c, "gcc")) { destination[0] == '\0' && (destination = "out_gcc"); compiler_cpp = "g++"; } else if (str_eq(compiler_c, "clang")) { destination[0] == '\0' && (destination = "out_clang"); compiler_cpp = "clang++"; } else if (str_eq_lower(compiler_c, "cl") || str_eq_lower(compiler_c, "cl.exe")) { destination[0] == '\0' && (destination = "out_cl"); compiler_cpp = compiler_c; postfix_obj = ".obj"; flag_obj = "-c -Fo"; flag_exe = "-Fe"; } else if (str_eq(compiler_c, "emcc")) { destination[0] == '\0' && (destination = "out_emcc"); compiler_cpp = "em++"; postfix_exe = ".js"; run_tests = 0; } if (str_eq_lower(compiler_c, "cl") || str_eq_lower(compiler_c, "cl.exe")) { if (str_eq_lower(build_type, "release")) flags = "-O2 -DNDEBUG"; else flags = "-Od"; } else if (str_eq(compiler_c, "clang")) { if (str_eq_lower(build_type, "release")) flags = "-O3 -DNDEBUG"; else flags = "-O0"; } else { if (str_eq_lower(build_type, "release")) flags = "-O3 -DNDEBUG"; else if (OS != WINDOWS && str_eq(compiler_c, "gcc") && !STATIC_RUNTIME) flags = "-Wall -Wextra -Wno-missing-braces -Wno-missing-field-initializers -Werror -pedantic -O0 -fsanitize=undefined,address,leak -mshstk"; else flags = "-Wall -Wextra -Wno-missing-braces -Wno-missing-field-initializers -Werror -pedantic -O0"; } if (OS == WINDOWS) { if (str_eq_lower(compiler_c, "cl") || str_eq_lower(compiler_c, "cl.exe")) { if (str_eq_lower(build_type, "debug")) link_flags = #if REQUIRE_SOCKETS "Ws2_32.lib " #endif "Shlwapi.lib Advapi32.lib " #if STATIC_RUNTIME "/MTd " #endif ""; else link_flags = #if REQUIRE_SOCKETS "Ws2_32.lib " #endif "Shlwapi.lib Advapi32.lib " #if STATIC_RUNTIME "/MT " #endif ""; } else link_flags = #if REQUIRE_SOCKETS "-lWs2_32 " #endif "-lShlwapi -lAdvapi32" #if STATIC_RUNTIME "-static " #endif ""; } if (OS == LINUX) link_flags = #if REQUIRE_THREADS "-pthread " #endif #if REQUIRE_MATH "-lm " #endif #if REQUIRE_DL "-ldl " #endif #if REQUIRE_GRAPHICS "-lX11 -lXi -lXcursor " #endif #if REQUIRE_OPENGL "-lGL " #endif #if REQUIRE_VULKAN "-lvulkan " #endif #if STATIC_RUNTIME "-static " #endif ""; if (str_eq(compiler_c, "emcc")) link_flags = #if REQUIRE_OPENGL "-sFULL_ES3=1 " #endif ""; if (extra_options[0] != '\0') flags = fmt_dup("%s %s", extra_options, flags); if (extra_link_options[0] != '\0') link_flags = fmt_dup("%s %s", extra_link_options, link_flags); // Prepare destination folder // destination[0] == '\0' && (destination = "out"); if (OS == WINDOWS) system(fmt("if not exist %s mkdir %s", destination, destination)); else system(fmt("mkdir %s -p", destination)); // Build the project // printf("\nCompiler options: %s\n", flags); printf( "Link options: %s\n\n", link_flags); fflush(stdout); printf("Build " PROJECT " library\n"); fflush(stdout); if (system(fmt( "%s %s " "%s%s" DLM PROJECT "%s " SOURCE_LIB, compiler_c, flags, flag_obj, destination, postfix_obj) ) != 0) return 1; fflush(stdout); printf("Build the test suite\n"); fflush(stdout); if (system(fmt( "%s %s " "%s%s" DLM "test_suite%s " "%s" DLM PROJECT "%s " FOLDER_TESTS "_exe.c " "%s", compiler_c, flags, flag_exe, destination, postfix_exe, destination, postfix_obj, link_flags) ) != 0) return 1; fflush(stdout); c8 *c_tests[] = { "test_too_many_assertions", "test_too_many_tests", "test_interprocess", }; for (i32 i = 0; i < sizeof c_tests / sizeof *c_tests; ++i) if (system(fmt( "%s %s " "%s%s" DLM "%s%s " "%s" DLM PROJECT "%s " FOLDER_TESTS "%s.c" " %s", compiler_c, flags, flag_exe, destination, c_tests[i], postfix_exe, destination, postfix_obj, c_tests[i], link_flags) ) != 0) return 1; fflush(stdout); c8 *cpp_tests[] = { "test_cpp", "test_signals", }; for (i32 i = 0; i < sizeof cpp_tests / sizeof *cpp_tests; ++i) if (system(fmt( "%s %s " "%s%s" DLM "%s%s " "%s" DLM PROJECT "%s " FOLDER_TESTS "%s.cpp" " %s", compiler_cpp, flags, flag_exe, destination, cpp_tests[i], postfix_exe, destination, postfix_obj, cpp_tests[i], link_flags) ) != 0) return 1; fflush(stdout); if (!run_tests) return 0; // Run tests // i32 status = 0; printf("Run tests\n\n"); fflush(stdout); if (system(fmt("\"." DLM "%s" DLM "test_suite\"", destination)) != 0) status = 1; fflush(stdout); i32 code; code = system(fmt("\"." DLM "%s" DLM "test_too_many_assertions\" --quiet", destination)) & 0xff; if (code == 0) printf("too many assertions - OK\n"); else { printf("too many assertions - FAILED (code %d)\n", code); status = 1; } fflush(stdout); code = system(fmt("\"." DLM "%s" DLM "test_too_many_tests\" --quiet", destination)) & 0xff; if (code == 0) printf("too many tests - OK\n"); else { printf("too many tests - FAILED (code %d)\n", code); status = 1; } fflush(stdout); code = system(fmt("\"." DLM "%s" DLM "test_cpp\" --quiet", destination)) & 0xff; if (code == 0) printf("cpp - OK\n"); else { printf("cpp - FAILED (code %d)\n", code); status = 1; } fflush(stdout); code = system(fmt("\"." DLM "%s" DLM "test_signals\" --quiet", destination)) & 0xff; if (code == 0) printf("signals - OK\n"); else { printf("signals - FAILED (code %d)\n", code); status = 1; } fflush(stdout); system(fmt("\"." DLM "%s" DLM "test_interprocess\" clean", destination)); #if defined(_WIN32) && !defined(__CYGWIN__) STARTUPINFO si = { .cb = sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi = { 0 }; CreateProcessA( NULL, fmt("\"." DLM "%s" DLM "test_interprocess\" reader", destination), NULL, NULL, 0, 0, NULL, NULL, &si, &pi ); Sleep(0); #else system(fmt("\"." DLM "%s" DLM "test_interprocess\" reader &", destination)); #endif code = system(fmt("\"." DLM "%s" DLM "test_interprocess\" writer", destination)); if (code == 0) printf("interprocess - OK\n"); else { printf("interprocess - FAILED (code %d)\n", code); status = 1; } fflush(stdout); #if defined(_WIN32) && !defined(__CYGWIN__) WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); #endif if (status == 0) printf("\nAll done - OK.\n"); else printf("\nAll done - FAILURE.\n"); return status; }