From 07ff1d2d6efb36332622762a9de8c7237fd25d5c Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Thu, 6 Jun 2024 12:06:03 +0200 Subject: Add build script in C --- build.c | 547 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100755 build.c (limited to 'build.c') diff --git a/build.c b/build.c new file mode 100755 index 0000000..38a1462 --- /dev/null +++ b/build.c @@ -0,0 +1,547 @@ +#if 0 +SRC=${0##*/} +BIN=${SRC%.*} +gcc -fsanitize=undefined,address,leak -o $BIN.tmp $SRC && ./$BIN.tmp $@ && rm $BIN.tmp +exit 0 +#endif + +#include +#include +#include +#include +#include + +#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__) +enum { OS = WINDOWS, }; +#elif defined(__APPLE__) +enum { OS = MACOS, }; +#else +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" + ); +} + +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 make_fmt_v(c8 const *format, va_list args) { + vsprintf(_strings[_string_index], format, args); +} + +c8 *make_fmt(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]); + } + } + + // 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; + } + + // 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 = "-O0 -fsanitize=undefined,address,leak"; + else + flags = "-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 = make_fmt("%s %s", extra_options, flags); + if (extra_link_options[0] != '\0') + link_flags = make_fmt("%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); + + printf("Build " PROJECT " library\n"); + + if (system(fmt( + "%s %s " + "%s%s/" PROJECT "%s " + SOURCE_LIB, + compiler_c, flags, + flag_obj, destination, postfix_obj) + ) != 0) + return 1; + + printf("Build the test suite\n"); + + if (system(fmt( + "%s %s " + "%s%s/test_suite%s " + "%s/" PROJECT "%s " + FOLDER_TESTS "_exe.c " + "%s", + compiler_c, flags, + flag_exe, destination, postfix_exe, + destination, postfix_obj, + link_flags) + ) != 0) + return 1; + + 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/%s%s " + "%s/" 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; + + 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/%s%s " + "%s/" 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; + + if (!run_tests) + return 0; + + // Run tests + // + + i32 status = 0; + + printf("Run tests\n\n"); + + if (system(fmt("%s/test_suite", destination)) != 0) + status = 1; + + i32 code; + + code = system(fmt("%s/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; + } + + code = system(fmt("%s/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; + } + + code = system(fmt("%s/test_cpp --quiet", destination)) & 0xff; + if (code == 0) + printf("cpp - OK\n"); + else { + printf("cpp - FAILED (code %d)\n", code); + status = 1; + } + + code = system(fmt("%s/test_signals --quiet", destination)) & 0xff; + if (code == 0) + printf("signals - OK\n"); + else { + printf("signals - FAILED (code %d)\n", code); + status = 1; + } + + system(fmt("%s/test_interprocess clean", destination)); + if (OS == WINDOWS) + system(fmt("start \"\" /b %s/test_interprocess reader", destination)); + else + system(fmt("%s/test_interprocess reader &", destination)); + code = system(fmt("%s/test_interprocess writer", destination)); + if (code == 0) + printf("interprocess - OK\n"); + else { + printf("interprocess - FAILED (code %d)\n", code); + status = 1; + } + + printf("\nAll done.\n"); + return status; +} -- cgit v1.2.3