about summary refs log tree commit diff
path: root/boot
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-01-26 03:15:18 +0100
committerMel <mel@rnrd.eu>2026-01-26 03:15:18 +0100
commit38bc2d06fff322a99ba6ba46df69bbd2b40c6173 (patch)
tree0485c271240bdde9d5cded82623b76e734fe9f70 /boot
parent4dc6b49db40eaebf700d5c26c93c5f33b3db60ac (diff)
downloadcatskill-38bc2d06fff322a99ba6ba46df69bbd2b40c6173.tar.zst
catskill-38bc2d06fff322a99ba6ba46df69bbd2b40c6173.zip
Streamline build and temporary build directory, copy runtime
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot')
-rw-r--r--boot/build.c162
-rw-r--r--boot/catboot.c89
-rw-r--r--boot/common.c8
-rwxr-xr-xboot/scripts/embed.sh2
4 files changed, 198 insertions, 63 deletions
diff --git a/boot/build.c b/boot/build.c
index 1c95414..1a1cb21 100644
--- a/boot/build.c
+++ b/boot/build.c
@@ -28,6 +28,30 @@
 #error "MUSL_DEV not defined"
 #endif
 
+bool
+create_build_directory(struct String* build_path)
+{
+    // we can't directly call mkdtemp to create nested directories,
+    // so we create the parent /tmp/catskill/ first.
+    ascii build_dir_template[] = "/tmp/catskill/build_XXXXXX";
+
+    struct stat st;
+    if (stat("/tmp/catskill", &st) == -1) {
+        if (mkdir("/tmp/catskill", 0700) == -1) {
+            log_error("failed to create /tmp/catskill/ directory\n");
+            return false;
+        }
+    }
+
+    ascii* build_dir_path = mkdtemp(build_dir_template);
+    if (!build_dir_path) { return false; }
+
+    *build_path = string_from_c_string(build_dir_path);
+    return true;
+}
+
+// TODO: clean up build directory after!
+
 struct Compiler_Command_Result
 {
     integer exit_code;
@@ -47,7 +71,9 @@ run_compiler_command(const ascii* command)
 
     while ((bytes_read = fread(log_buffer, 1, ARRAY_SIZE(log_buffer), command_pipe)) > 0) {
         if (bytes_read >= ARRAY_SIZE(log_buffer)) {
-            log_error("compiler output surpassed maximum output length... truncating to %lu bytes\n", ARRAY_SIZE(log_buffer) - 1);
+            log_error(
+                "compiler output surpassed maximum output length... truncating to %lu bytes\n",
+                ARRAY_SIZE(log_buffer) - 1);
             break;
         }
     }
@@ -58,6 +84,79 @@ run_compiler_command(const ascii* command)
     return (struct Compiler_Command_Result){ .exit_code = exit_code, .log = log };
 }
 
+// TODO: move this over to common library
+bool
+write_build_file(struct String source, struct String input_path)
+{
+    FILE* input_file = fopen(string_c_str(input_path), "w");
+    if (!input_file) { return false; }
+
+    fwrite(source.data, 1, source.length, input_file);
+    fclose(input_file);
+    return true;
+}
+
+bool
+copy_runtime_library(struct String build_path)
+{
+    const ascii core[] = CATSKILL_EMBED("./boot/runtime/core.c");
+    const ascii runtime[] = CATSKILL_EMBED("./boot/runtime/runtime.c");
+    const ascii stubs[] = CATSKILL_EMBED("./boot/runtime/stubs.c");
+
+    struct String build_runtime_path = string_append_c_str(build_path, "/runtime/");
+    if (mkdir(string_c_str(build_runtime_path), 0700) == -1) {
+        log_error("failed to create build runtime directory\n");
+        return false;
+    }
+
+    struct String
+        core_path = string_append_c_str(build_runtime_path, "core.c"),
+        runtime_path = string_append_c_str(build_runtime_path, "runtime.c"),
+        stubs_path = string_append_c_str(build_runtime_path, "stubs.c");
+
+    struct String
+        core_source = string_from_c_string(core),
+        runtime_source = string_from_c_string(runtime), stubs_source = string_from_c_string(stubs);
+
+    if (!write_build_file(core_source, core_path)) return false;
+    if (!write_build_file(runtime_source, runtime_path)) return false;
+    if (!write_build_file(stubs_source, stubs_path)) return false;
+
+    return true;
+}
+
+bool
+copy_file(struct String source_path, struct String dest_path)
+{
+    FILE* source_file = fopen(string_c_str(source_path), "rb");
+    if (!source_file) {
+        log_error("failed to open source file for copying: %s\n", source_path);
+        return false;
+    }
+
+    // create destination file with the right permissions first
+    if (!creat(string_c_str(dest_path), 0700)) {
+        log_error("failed to create destination file for copying: %s\n", dest_path);
+        fclose(source_file);
+        return false;
+    }
+    FILE* dest_file = fopen(string_c_str(dest_path), "wb");
+    if (!dest_file) {
+        log_error("failed to open destination file for copying: %s\n", dest_path);
+        fclose(source_file);
+        return false;
+    }
+
+    ascii buffer[4096];
+    size_t bytes;
+    while ((bytes = fread(buffer, 1, sizeof(buffer), source_file)) > 0) {
+        fwrite(buffer, 1, bytes, dest_file);
+    }
+    fclose(source_file);
+    fclose(dest_file);
+    return true;
+}
+
 struct Build_Result
 {
     bool success;
@@ -68,15 +167,10 @@ struct Build_Result
 };
 
 struct Build_Result
-build_executable(struct String build_path)
+compile_using_backend(
+    struct String build_path, struct String source_path, struct String output_path)
 {
-
-    struct String source_path = string_append_c_str(build_path, "/input.c");
-    struct String output_path = string_append_c_str(build_path, "/output");
-
-    ascii* compiler = "clang";
-
-    log_debug("current backend: %s\n", compiler);
+    log_debug("current backend: LLVM Clang (CLI)\n");
 
     // TODO: right now we always run the compiler from this source directory,
     // so we can refer to the runtime libraries by a relative path "./boot/runtime/",
@@ -87,8 +181,10 @@ build_executable(struct String build_path)
     // this simple bootstrapping output to be the eventual target
     // of our self-hosted compiler stack, so we avoid anything fancy
     // for an easier target to hit.
+    struct String runtime_path = string_append_c_str(build_path, "/runtime/");
+    struct String stubs_path = string_append_c_str(runtime_path, "stubs.c");
     const ascii* arguments[] = {
-        compiler,
+        "clang",
 
         // high-level compiler behavior
         "-O0",
@@ -116,19 +212,22 @@ build_executable(struct String build_path)
         // linker and header options, static compilation w/ musl
         "-static",
         "-nostdlib",
-        "-I./boot/runtime/",
+        "-I",
+        string_c_str(runtime_path),
         "-isystem",
         MUSL_DEV "/include",
         // linking must follow this exact order
         MUSL_LIB "/lib/crt1.o",
         MUSL_LIB "/lib/crti.o",
-        "./boot/runtime/stubs.c", // stub out some software float implementations
+        string_c_str(stubs_path), // stub out some software float implementations
         string_c_str(source_path),
         MUSL_LIB "/lib/libc.a",
         MUSL_LIB "/lib/crtn.o",
 
         "-o",
         string_c_str(output_path),
+
+        "2>&1", // errors are output to stderr, we want to capture them
     };
 
     // append all flags to single command
@@ -147,3 +246,42 @@ build_executable(struct String build_path)
         .output_path = output_path,
     };
 }
+
+struct Build_Result
+build_executable(struct String source, struct String output_path)
+{
+    struct String build_path;
+    if (!create_build_directory(&build_path)) {
+        log_error("failed to create build directory\n");
+        return (struct Build_Result){};
+    }
+    log_debug("temporary build directory for compilation: %s\n", build_path);
+
+    // temporary input and output paths
+    struct String build_input = string_append_c_str(build_path, "/input.c");
+    struct String build_output = string_append_c_str(build_path, "/output");
+
+    if (!write_build_file(source, build_input)) {
+        log_error("failed to create input source file\n");
+        return (struct Build_Result){};
+    }
+
+    // the runtime library files need to be present in the build directory
+    // for inclusion within the generated c source.
+    if (!copy_runtime_library(build_path)) {
+        log_error("failed to copy runtime library files\n");
+        return (struct Build_Result){};
+    }
+
+    struct Build_Result result = compile_using_backend(build_path, build_input, build_output);
+    if (!result.success) { return result; }
+
+    // copy the output executable to the desired location
+    if (!copy_file(build_output, output_path)) {
+        log_error("failed to copy final executable to %s\n", output_path);
+        return (struct Build_Result){};
+    }
+
+    result.output_path = output_path;
+    return result;
+}
diff --git a/boot/catboot.c b/boot/catboot.c
index 40756ac..db100dd 100644
--- a/boot/catboot.c
+++ b/boot/catboot.c
@@ -20,12 +20,6 @@
 
 #define _POSIX_C_SOURCE 200809L
 
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-
 #include "catboot.h"
 
 #define VERSION "0.0.0"
@@ -206,11 +200,33 @@ test_transpile_command(struct Command_Arguments* arguments)
     return COMMAND_OK;
 }
 
+struct String
+output_name_from_input_name(struct String input)
+{
+    uint end = string_length(input);
+    for (uint start = string_length(input) - 1; start > 0; --start) {
+        switch (string_at(input, start)) {
+        // we captured the file extension, we need to keep going until we find a slash,
+        // but we move the end index to the current index first to exclude the extension.
+        case '.':
+            end = start;
+            break;
+        // we found a slash, so we captured the first part of the filename,
+        // we can stop here.
+        case '/':
+            return string_slice(input, start + 1, end); // exclude the slash itself
+        // otherwise, keep going.
+        default:
+            break;
+        }
+    }
+
+    return string_slice(input, 0, end);
+}
+
 enum Command_Result
 default_command(struct Command_Arguments* arguments)
 {
-    enum Command_Result result = COMMAND_OK;
-
     struct String source = read_file(arguments->input);
     struct Source_File source_file = {
         .source = source,
@@ -226,61 +242,38 @@ default_command(struct Command_Arguments* arguments)
     struct Tree tree;
     if (parser_do_your_thing(&parser, &tree) != 0) {
         log_error("parser finished with errors\n");
-
-        result = COMMAND_FAIL;
-        goto end;
+        return COMMAND_FAIL;
     }
 
-    ascii build_directory_template[] = "/tmp/catboot-build-XXXXXX"; // TODO: /tmp/catboot/build-XXXXXX
-
-    ascii* build_directory_path = mkdtemp(build_directory_template);
-    struct String build_path = string_from_c_string(build_directory_path);
-    struct String source_path = string_append_c_str(build_path, "/input.c");
-
-    FILE* output_file = fopen(string_c_str(source_path), "w");
-    if (!output_file) {
-        log_error("could not open temporary output file: %s\n", string_c_str(source_path));
-
-        result = COMMAND_FAIL;
-        goto end;
-    }
-
-    struct Transpile_Output output = transpile_output_from_file(output_file);
-
     struct Transpiler transpiler;
-    transpiler_new(&transpiler, output);
+    transpiler_new(&transpiler, transpile_output_from_string());
 
     if (transpiler_catskill_to_c(&transpiler, &tree) != 0) {
         log_error("transpiler finished with errors\n");
-        fclose(output_file);
+        return COMMAND_FAIL;
+    }
+    struct String transpiled_source = transpile_output_string(&transpiler.output);
 
-        result = COMMAND_FAIL;
-        goto end;
+    struct String output_file;
+    if (arguments->output) {
+        output_file = string_from_c_string(arguments->output);
+    } else {
+        output_file = output_name_from_input_name(source_file.path);
     }
-    fclose(output_file);
 
-    struct Build_Result build_result = build_executable(build_path);
+    struct Build_Result build_result = build_executable(transpiled_source, output_file);
     if (!build_result.success) {
         log_error("backend failure with exit code %ld\n", build_result.compiler_exit_code);
-        log_error("compiler output:\n%.*s\n", (int)build_result.compiler_log.length, build_result.compiler_log.data);
+        log_error(
+            "compiler output:\n%.*s\n", (int)build_result.compiler_log.length,
+            build_result.compiler_log.data);
 
-        result = COMMAND_FAIL;
-        goto end;
+        return COMMAND_FAIL;
     }
 
-    // copy the output executable to the desired location
-    const ascii* output_filename = arguments->output ? arguments->output : "a.out";
+    log_debug("success! executable written to '%s'.\n", string_c_str(build_result.output_path));
 
-    // simple copy command for now
-    ascii copy_command[2048];
-    snprintf(copy_command, sizeof(copy_command), "cp %s %s", string_c_str(build_result.output_path), output_filename);
-    if (system(copy_command) != 0) {
-        log_error("failed to copy executable to %s\n", output_filename);
-        result = COMMAND_FAIL;
-    }
-
-end:
-    return result;
+    return COMMAND_OK;
 }
 
 typedef enum Command_Result (*Command_Function)(struct Command_Arguments*);
diff --git a/boot/common.c b/boot/common.c
index 1c65deb..125af86 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -10,6 +10,7 @@
 
 #pragma once
 
+#include <fcntl.h>
 #include <math.h>
 #include <stdarg.h>
 #include <stddef.h>
@@ -17,6 +18,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #define uint8 uint8_t
@@ -53,7 +57,7 @@
 // the array will be filled with hexadecimal values of the file contents,
 // with a nil byte appended at the end.
 // the size of the array can be retrieved through `strlen(array)`, or `ARRAY_SIZE(array)-1`.
-#define CATSKILL_EMBED(...) {0}
+#define CATSKILL_EMBED(...) { 0 }
 
 // ansi escape codes for terminal color and style
 #define ANSI(code) "\33[" code "m"
@@ -188,7 +192,7 @@ log_error(const ascii* message, ...)
 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
 
 // the common size of region memory blocks.
-#define REGION_SIZE 65536
+#define REGION_SIZE 1048576
 
 // statically allocates a region of memory of a given size
 // for a single type.
diff --git a/boot/scripts/embed.sh b/boot/scripts/embed.sh
index f27c2a6..baf2e38 100755
--- a/boot/scripts/embed.sh
+++ b/boot/scripts/embed.sh
@@ -23,7 +23,7 @@ process_file() {
 
     cp "$source_file" "$output_file"
 
-    local embed_regex='CATSKILL_EMBED\(([^)]+)\)' # const byte data[] = CATSKILL_EMBED(./file);
+    local embed_regex='CATSKILL_EMBED\("([^)]+)"\)' # const byte data[] = CATSKILL_EMBED("./file");
     local comment_regex='^\s*//' # // the macro CATSKILL_EMBED does...
     local define_regex='^\s*#define' # #define CATSKILL_EMBED {0}