summaryrefslogtreecommitdiff
path: root/build.c
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2024-06-06 12:06:03 +0200
committerMitya Selivanov <automainint@guattari.tech>2024-06-06 12:06:03 +0200
commit07ff1d2d6efb36332622762a9de8c7237fd25d5c (patch)
tree6ead8bb177efa0dfb3e98c1ed3dd1245ee3288ce /build.c
parentc764ffdb7d580f12327a788c8a5b1b4b011870aa (diff)
downloadkit-07ff1d2d6efb36332622762a9de8c7237fd25d5c.zip
Add build script in C
Diffstat (limited to 'build.c')
-rwxr-xr-xbuild.c547
1 files changed, 547 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#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;
+}