From e18e2354c339150d5bf9728788692456e774d88c Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 09:52:41 +0100 Subject: [PATCH 01/22] bob.fl: Write documentation for settable variables --- import/bob.fl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/import/bob.fl b/import/bob.fl index e743b3a2..8d665d58 100644 --- a/import/bob.fl +++ b/import/bob.fl @@ -61,6 +61,16 @@ class Platform { static proto getenv(key: str) -> str } +# Vector of other projects this project depends on. let deps: vec = [] + +# Map between cookies or source files and their corresponding (prefixless) install paths. let install: map + +# Vector of the default run command for when using `bob run`. +# This is usually a command in the temporary install prefix. +# For example, if my project was the echo(1) POSIX command, the default run command could be `["echo"]`, such that I could run `bob run "Hello, world!"`. +# +# If this is set to `none`, running will be disabled and Bob will emit an error if the user tries to run `bob run`. +# This is useful for projects that are not meant to be run directly, such as libraries. let run: vec = [] From 6899a55b0387fedcf7024adbda74ff27b30ef5b7 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 09:52:50 +0100 Subject: [PATCH 02/22] bob.fl: Write documentation for `Platform` --- import/bob.fl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/import/bob.fl b/import/bob.fl index 8d665d58..0ec9dc1c 100644 --- a/import/bob.fl +++ b/import/bob.fl @@ -56,7 +56,10 @@ class Linker(flags: vec) { proto archive(obj: vec) -> str } +# This class provides platform-specific information. class Platform { + # Return the OS of the platform Bob is running on. + # The output of this function is equivalent to the output of the uname(1) POSIX command. static proto os -> str static proto getenv(key: str) -> str } From 2d10ea4ec78172eaa3bda1fc3b151b5b1098c0f5 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 09:53:04 +0100 Subject: [PATCH 03/22] bob.fl: Initial draft for `Aquarium` --- import/bob.fl | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/import/bob.fl b/import/bob.fl index 0ec9dc1c..c55a7ee1 100644 --- a/import/bob.fl +++ b/import/bob.fl @@ -64,6 +64,33 @@ class Platform { static proto getenv(key: str) -> str } +# This class provides a way to build and interact with aquariums. +# Bob does not come with aquariums by default, so you must have already installed it. +# It uses the aquarium command line tool to build aquariums, so you don't need to re-link it if you installed aquariums a posteriori. +# +# The constructor just creates a new aquarium from a template. +class Aquarium(template: str) { + # Builder aquarium stuff. + + # Create a builder aquarium from a template and a project path. + # The builder aquarium will be created from the template and will have Bob installed in it. + # Then, the project at the given path will be copied over to the builder aquarium and be built within that aquarium. + static proto builder(template: str, project_path: str) -> Aquarium + + # Install the built project to a target aquarium. + # The aquarium this is called on must be a builder aquarium, i.e. one created with `Aquarium.builder()`. + proto install_to(target: Aquarium) -> void + + # Regular aquarium stuff. + + # Add a kernel template to the aquarium. + proto add_kernel(kernel: str) -> void + + # Create a bootable image from the aquarium. + # A kernel must have been added to the aquarium with `set_kernel()` before calling this method. + proto image -> str +} + # Vector of other projects this project depends on. let deps: vec = [] From dc278508a97d7924e877599029ed9e5e61a1a7b8 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 09:53:30 +0100 Subject: [PATCH 04/22] bob.fl: Pull out `AquariumBuilder` (much saner) --- import/bob.fl | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/import/bob.fl b/import/bob.fl index c55a7ee1..a2874e3c 100644 --- a/import/bob.fl +++ b/import/bob.fl @@ -64,25 +64,12 @@ class Platform { static proto getenv(key: str) -> str } -# This class provides a way to build and interact with aquariums. +# This class provides a way to create and interact with aquariums. # Bob does not come with aquariums by default, so you must have already installed it. # It uses the aquarium command line tool to build aquariums, so you don't need to re-link it if you installed aquariums a posteriori. # # The constructor just creates a new aquarium from a template. class Aquarium(template: str) { - # Builder aquarium stuff. - - # Create a builder aquarium from a template and a project path. - # The builder aquarium will be created from the template and will have Bob installed in it. - # Then, the project at the given path will be copied over to the builder aquarium and be built within that aquarium. - static proto builder(template: str, project_path: str) -> Aquarium - - # Install the built project to a target aquarium. - # The aquarium this is called on must be a builder aquarium, i.e. one created with `Aquarium.builder()`. - proto install_to(target: Aquarium) -> void - - # Regular aquarium stuff. - # Add a kernel template to the aquarium. proto add_kernel(kernel: str) -> void @@ -91,6 +78,16 @@ class Aquarium(template: str) { proto image -> str } +# This class provides a way to build projects inside of aquariums. +# All the same disclaimers from the `Aquarium` class apply here too. +# +# The constructor creates a builder aquarium from the template and will have Bob installed to it. +# Then, the project at the given path will be copied over to the builder aquarium and be built within that aquarium. +class AquariumBuilder(template: str, project_path: str) { + # Install the built project to a target aquarium. + proto install_to(target: Aquarium) -> void +} + # Vector of other projects this project depends on. let deps: vec = [] From 59926bd16501b94f5a27ba18706b079cfd1becd0 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 10:02:36 +0100 Subject: [PATCH 05/22] aquarium: Boilerplate for `Aquarium` class implementation --- src/class/aquarium.c | 98 ++++++++++++++++++++++++++++++++++++++++++++ src/class/class.h | 2 + 2 files changed, 100 insertions(+) create mode 100644 src/class/aquarium.c diff --git a/src/class/aquarium.c b/src/class/aquarium.c new file mode 100644 index 00000000..16c3adc1 --- /dev/null +++ b/src/class/aquarium.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2024 Aymeric Wibo + +#include + +#include +#include +#include + +#include + +#define AQUARIUM "Aquarium" + +typedef struct { + char* template; + char* kernel; +} state_t; + +static int add_kernel(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { + assert(args->count == 1); + + if (args->args[0]->kind != FLAMINGO_VAL_KIND_STR) { + LOG_FATAL(AQUARIUM ": Expected argument to be a string"); + return -1; + } + + state->kernel = strndup(args->args[0]->str.str, args->args[0]->str.size); + assert(state->kernel != NULL); + + return 0; +} + +static int prep_image(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { + assert(args->count == 0); + + // TODO + + return 0; +} + +static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_val_t** rv, bool* consumed) { + *consumed = true; + + state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? + + if (flamingo_cstrcmp(callable->name, "add_kernel", callable->name_size) == 0) { + return add_kernel(state, args, rv); + } + + else if (flamingo_cstrcmp(callable->name, "image", callable->name_size) == 0) { + return prep_image(state, args, rv); + } + + *consumed = false; + return 0; +} + +static void free_state(flamingo_val_t* inst, void* data) { + state_t* const state = data; + + free(state->template); + free(state->kernel); + + free(state); +} + +static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { + assert(args->count == 1); + + if (args->args[0]->kind != FLAMINGO_VAL_KIND_STR) { + LOG_FATAL(AQUARIUM ": Expected argument to be a string"); + return -1; + } + + // Create state object. + + state_t* const state = malloc(sizeof *state); + assert(state != NULL); + + state->template = strndup(args->args[0]->str.str, args->args[0]->str.size); + assert(state->template != NULL); + + state->kernel = NULL; + + inst->inst.data = state; + inst->inst.free_data = free_state; + + // TODO Add a build step to create aquarium itself. + + return 0; +} + +bob_class_t BOB_CLASS_AQUARIUM = { + .name = AQUARIUM, + .populate = NULL, + .call = call, + .instantiate = instantiate, +}; diff --git a/src/class/class.h b/src/class/class.h index ffa0b21e..25204cdc 100644 --- a/src/class/class.h +++ b/src/class/class.h @@ -14,6 +14,7 @@ typedef struct { int (*instantiate)(flamingo_val_t* inst, flamingo_arg_list_t* args); } bob_class_t; +extern bob_class_t BOB_CLASS_AQUARIUM; extern bob_class_t BOB_CLASS_CC; extern bob_class_t BOB_CLASS_CARGO; extern bob_class_t BOB_CLASS_FS; @@ -23,6 +24,7 @@ extern bob_class_t BOB_CLASS_PKG_CONFIG; extern bob_class_t BOB_CLASS_PLATFORM; static bob_class_t* const BOB_CLASSES[] = { + &BOB_CLASS_AQUARIUM, &BOB_CLASS_CC, &BOB_CLASS_CARGO, &BOB_CLASS_FS, From 8f52b8c083327f8c85655da1bdecc232ee988d62 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 10:07:40 +0100 Subject: [PATCH 06/22] aquarium: Boilerplate for `AquariumBuilder` class implementation --- src/class/aquarium.c | 2 +- src/class/aquarium_builder.c | 96 ++++++++++++++++++++++++++++++++++++ src/class/class.h | 2 + 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/class/aquarium_builder.c diff --git a/src/class/aquarium.c b/src/class/aquarium.c index 16c3adc1..22b0a557 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -33,7 +33,7 @@ static int add_kernel(state_t* state, flamingo_arg_list_t* args, flamingo_val_t* static int prep_image(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 0); - // TODO + // TODO Imaging build step. return 0; } diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c new file mode 100644 index 00000000..6355584d --- /dev/null +++ b/src/class/aquarium_builder.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2024 Aymeric Wibo + +#include +#include +#include +#include + +#include + +#define AQUARIUM_BUILDER "AquariumBuilder" + +typedef struct { + char* template; + char* project_path; +} state_t; + +static int prep_install_to(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { + assert(args->count == 1); + flamingo_val_t* const arg = args->args[0]; + + if (arg->kind != FLAMINGO_VAL_KIND_INST) { + LOG_FATAL(AQUARIUM_BUILDER ": Expected argument to be an instance of the Aquarium class"); + return -1; + } + + if (flamingo_cstrcmp(arg->inst.class->name, "Aquarium", arg->inst.class->name_size) != 0) { + LOG_FATAL(AQUARIUM_BUILDER ": Expected argument to be an instance of the Aquarium class (is instance of '%.*s' instead)", (int) arg->inst.class->name_size, arg->inst.class->name); + return -1; + } + + // TODO Installing build step. + + return 0; +} + +static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_val_t** rv, bool* consumed) { + *consumed = true; + + state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? + + if (flamingo_cstrcmp(callable->name, "install_to", callable->name_size) == 0) { + return prep_install_to(state, args, rv); + } + + *consumed = false; + return 0; +} + +static void free_state(flamingo_val_t* inst, void* data) { + state_t* const state = data; + + free(state->template); + free(state->project_path); + + free(state); +} + +static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { + assert(args->count == 2); + + if (args->args[0]->kind != FLAMINGO_VAL_KIND_STR) { + LOG_FATAL(AQUARIUM_BUILDER ": Expected template argument to be a string"); + return -1; + } + + if (args->args[1]->kind != FLAMINGO_VAL_KIND_STR) { + LOG_FATAL(AQUARIUM_BUILDER ": Expected project path argument to be a string"); + return -1; + } + + // Create state object. + + state_t* const state = malloc(sizeof *state); + assert(state != NULL); + + state->template = strndup(args->args[0]->str.str, args->args[0]->str.size); + assert(state->template != NULL); + + state->project_path = strndup(args->args[0]->str.str, args->args[0]->str.size); + assert(state->project_path != NULL); + + inst->inst.data = state; + inst->inst.free_data = free_state; + + // TODO Add build step to create aquarium itself. + + return 0; +} + +bob_class_t BOB_CLASS_AQUARIUM_BUILDER = { + .name = AQUARIUM_BUILDER, + .populate = NULL, + .call = call, + .instantiate = instantiate, +}; diff --git a/src/class/class.h b/src/class/class.h index 25204cdc..98c50708 100644 --- a/src/class/class.h +++ b/src/class/class.h @@ -15,6 +15,7 @@ typedef struct { } bob_class_t; extern bob_class_t BOB_CLASS_AQUARIUM; +extern bob_class_t BOB_CLASS_AQUARIUM_BUILDER; extern bob_class_t BOB_CLASS_CC; extern bob_class_t BOB_CLASS_CARGO; extern bob_class_t BOB_CLASS_FS; @@ -25,6 +26,7 @@ extern bob_class_t BOB_CLASS_PLATFORM; static bob_class_t* const BOB_CLASSES[] = { &BOB_CLASS_AQUARIUM, + &BOB_CLASS_AQUARIUM_BUILDER, &BOB_CLASS_CC, &BOB_CLASS_CARGO, &BOB_CLASS_FS, From fe9f9d6a1c971ef5cabb8ac9ce1159b85a952ce1 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 10:13:34 +0100 Subject: [PATCH 07/22] AquariumBuilder: Fix import ordering --- src/class/aquarium_builder.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 6355584d..93a4e563 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2024 Aymeric Wibo -#include #include + +#include #include #include From 83f0c6a424c9562f205537f0537df2936f820b80 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 15:49:46 +0100 Subject: [PATCH 08/22] AquariumBuilder: Create builder aquarium --- src/class/aquarium_builder.c | 77 ++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 93a4e563..343b563a 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -3,19 +3,80 @@ #include +#include #include +#include +#include #include #include #include +#include #define AQUARIUM_BUILDER "AquariumBuilder" +#define MAGIC (strhash("AquariumBuilder() magic")) typedef struct { char* template; char* project_path; + + // This is the aquarium itself. + // It shouldn't ever be returned anywhere, so it isn't ever copied. + + char* cookie; } state_t; +typedef struct { + state_t* state; +} bss_create_t; + +static int create_step(size_t data_count, void** data) { + // TODO Parallelize this, but make sure libaquarium works in parallel first. + + for (size_t i = 0; i < data_count; i++) { + bss_create_t* const bss = data[i]; + state_t* const state = bss->state; + + // Generate the cookie. + + uint64_t const hash = strhash(state->template) ^ strhash(state->project_path); + char* STR_CLEANUP cookie = NULL; + asprintf(&cookie, "%s/bob/aquarium_builder.cookie.%" PRIx64 ".aquarium", out_path, hash); + assert(cookie != NULL); + + // If it doesn't yet exist, create the aquarium. + + char* STR_CLEANUP pretty = NULL; + asprintf(&pretty, "Project '%s' with template '%s'", state->template, state->project_path); + assert(pretty != NULL); + + if (access(cookie, F_OK) == 0) { + log_already_done(cookie, pretty, "created builder aquarium"); + } + + else { + LOG_INFO("%s" CLEAR ": Creating builder aquarium, as it doesn't yet exist (this might take several minutes)...", pretty); + + cmd_t CMD_CLEANUP cmd; + cmd_create(&cmd, "aquarium", "-t", state->template, "create", cookie, NULL); + int const rv = cmd_exec(&cmd); + + if (rv == 0) { + set_owner(cookie); + } + + cmd_log(&cmd, cookie, pretty, "create builder aquarium", "created build aquarium", true); + + // TODO Install Bob. + } + + // TODO Copy/link over the project. + // TODO Attempt to build project. + } + + return 0; +} + static int prep_install_to(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 1); flamingo_val_t* const arg = args->args[0]; @@ -78,15 +139,25 @@ static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { state->template = strndup(args->args[0]->str.str, args->args[0]->str.size); assert(state->template != NULL); - state->project_path = strndup(args->args[0]->str.str, args->args[0]->str.size); + state->project_path = strndup(args->args[1]->str.str, args->args[1]->str.size); assert(state->project_path != NULL); inst->inst.data = state; inst->inst.free_data = free_state; - // TODO Add build step to create aquarium itself. + // Add build step to create aquarium itself. - return 0; + bss_create_t* const bss = malloc(sizeof *bss); + assert(bss != NULL); + + bss->state = state; + + // We can always do this in parallel as we can have no dependencies, so let's always merge these build steps. + // TODO In fact, I'm pretty sure we can run all the AquariumBuilder creation build steps program-wide. We don't need other build steps to fence this. + // TODO Is libaquarium happy with this? Either way it's its problem if it breaks in this situation. + // We also don't check for frugality here, because the project might have changes, so it'll always have to be rebuilt. + + return add_build_step(MAGIC, "Creating aquarium for aquarium builder", create_step, bss); } bob_class_t BOB_CLASS_AQUARIUM_BUILDER = { From bc463fba077780c8e5a6f0a03ccca5855cc24905 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 15:52:51 +0100 Subject: [PATCH 09/22] main: Error if all arguments are not consumed --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 8b0d4185..e0b4553b 100644 --- a/src/main.c +++ b/src/main.c @@ -391,7 +391,7 @@ int main(int argc, char* argv[]) { // This is intentionally undocumented, as it's really only used for communication between Bob parent processes and their Bob children processes. - else if (strcmp(instr, "dep-tree") == 0) { + else if (argc == 1 && strcmp(instr, "dep-tree") == 0) { LOG_WARN("This command is internal and isn't meant for direct use. A correct consumer of this command should be able to discard this message by reading the contents within the dependency tree tags."); if (bsys_dep_tree(bsys, argc, argv) == 0) { From d8ee6525125b6d779bd9e7619726a7f98320aabf Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 15:58:41 +0100 Subject: [PATCH 10/22] main: Do error if all arguments are not consumed better --- src/main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index e0b4553b..34f550d4 100644 --- a/src/main.c +++ b/src/main.c @@ -391,7 +391,12 @@ int main(int argc, char* argv[]) { // This is intentionally undocumented, as it's really only used for communication between Bob parent processes and their Bob children processes. - else if (argc == 1 && strcmp(instr, "dep-tree") == 0) { + else if (strcmp(instr, "dep-tree") == 0) { + if (argc != 0) { + LOG_FATAL("Extraneous arguments to '%s'.", instr); + usage(); + } + LOG_WARN("This command is internal and isn't meant for direct use. A correct consumer of this command should be able to discard this message by reading the contents within the dependency tree tags."); if (bsys_dep_tree(bsys, argc, argv) == 0) { From 8a13375c642d4714af4c0faeb26d35d1a2aca171 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 16:43:28 +0100 Subject: [PATCH 11/22] AquariumBuilder: Install Bob to the builder aquarium --- src/class/aquarium_builder.c | 39 +++++++++++++++++++++++++++++++++--- src/common.h | 2 ++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 343b563a..b2285764 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -47,7 +47,7 @@ static int create_step(size_t data_count, void** data) { // If it doesn't yet exist, create the aquarium. char* STR_CLEANUP pretty = NULL; - asprintf(&pretty, "Project '%s' with template '%s'", state->template, state->project_path); + asprintf(&pretty, "Project '%s' with template '%s'", state->project_path, state->template); assert(pretty != NULL); if (access(cookie, F_OK) == 0) { @@ -59,7 +59,8 @@ static int create_step(size_t data_count, void** data) { cmd_t CMD_CLEANUP cmd; cmd_create(&cmd, "aquarium", "-t", state->template, "create", cookie, NULL); - int const rv = cmd_exec(&cmd); + cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. + int rv = cmd_exec(&cmd); if (rv == 0) { set_owner(cookie); @@ -67,7 +68,39 @@ static int create_step(size_t data_count, void** data) { cmd_log(&cmd, cookie, pretty, "create builder aquarium", "created build aquarium", true); - // TODO Install Bob. + if (rv != 0) { + return -1; + } + + // Install Bob to the aquarium. + + LOG_INFO("%s" CLEAR ": Installing Bob to the builder aquarium...", pretty); + + cmd_free(&cmd); + cmd_create(&cmd, "aquarium", "-t", state->template, "enter", cookie, NULL); + + // clang-format off + cmd_prepare_stdin(&cmd, + "set -e\n" + "export HOME=/root\n" + "export PATH\n" + "pkg install -y git-lite\n" + "git clone https://github.com/inobulles/bob --depth 1 --branch " VERSION "\n" + "cd bob\n" + "sh bootstrap.sh\n" + ".bootstrap/bob install\n" + ); + // clang-format on + + cmd_set_redirect(&cmd, false); // It's nice to see these logs. + + if (cmd_exec(&cmd) < 0) { + LOG_FATAL("%s" CLEAR ": Failed to install Bob to the builder aquarium.", pretty); + rm(cookie, NULL); // To make sure that we go through this again next time the command is run. + return -1; + } + + LOG_SUCCESS("%s" CLEAR ": Installed Bob to the builder aquarium.", pretty); } // TODO Copy/link over the project. diff --git a/src/common.h b/src/common.h index 82256f97..2fcbd5ce 100644 --- a/src/common.h +++ b/src/common.h @@ -16,6 +16,8 @@ #define COMMON_INCLUDED +#define VERSION "v0.2.19" + /** * Whether the BOB_BUIlD_DEBUGGING envvar is set. */ From 84a83cc91efd5ef58fb1b89489ed078e293b8241 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 18:14:58 +0100 Subject: [PATCH 12/22] AquariumBuilder: Copy over project to aquarium --- src/class/aquarium_builder.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index b2285764..2ce5fc64 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -50,6 +50,8 @@ static int create_step(size_t data_count, void** data) { asprintf(&pretty, "Project '%s' with template '%s'", state->project_path, state->template); assert(pretty != NULL); + cmd_t CMD_CLEANUP cmd = {0}; + if (access(cookie, F_OK) == 0) { log_already_done(cookie, pretty, "created builder aquarium"); } @@ -57,7 +59,6 @@ static int create_step(size_t data_count, void** data) { else { LOG_INFO("%s" CLEAR ": Creating builder aquarium, as it doesn't yet exist (this might take several minutes)...", pretty); - cmd_t CMD_CLEANUP cmd; cmd_create(&cmd, "aquarium", "-t", state->template, "create", cookie, NULL); cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. int rv = cmd_exec(&cmd); @@ -101,9 +102,22 @@ static int create_step(size_t data_count, void** data) { } LOG_SUCCESS("%s" CLEAR ": Installed Bob to the builder aquarium.", pretty); + cmd_free(&cmd); + } + + // Copy over the project. + // TODO Should we preserve the .bob directory somehow? At least all the dependencies will be kept run-to-run. + + LOG_INFO("%s" CLEAR ": Copying project to the builder aquarium...", pretty); + + cmd_create(&cmd, "aquarium", "cp", cookie, state->project_path, "/proj", NULL); + int const rv = cmd_exec(&cmd); + cmd_log(&cmd, cookie, pretty, "copy project to builder aquarium", "copied project to builder aquarium", false); + + if (rv < 0) { + return -1; } - // TODO Copy/link over the project. // TODO Attempt to build project. } From 308d93a9676b88453997861046086bf9f4c8af41 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 18:15:13 +0100 Subject: [PATCH 13/22] dep-tree: Remove wrong extraneous argument check --- src/main.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main.c b/src/main.c index 34f550d4..8b0d4185 100644 --- a/src/main.c +++ b/src/main.c @@ -392,11 +392,6 @@ int main(int argc, char* argv[]) { // This is intentionally undocumented, as it's really only used for communication between Bob parent processes and their Bob children processes. else if (strcmp(instr, "dep-tree") == 0) { - if (argc != 0) { - LOG_FATAL("Extraneous arguments to '%s'.", instr); - usage(); - } - LOG_WARN("This command is internal and isn't meant for direct use. A correct consumer of this command should be able to discard this message by reading the contents within the dependency tree tags."); if (bsys_dep_tree(bsys, argc, argv) == 0) { From 69a65bd7105b386f90c1ba18e72e598f5cfe8216 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 18:21:47 +0100 Subject: [PATCH 14/22] AquariumBuilder: Actually attempt to build project in aquarium --- src/class/aquarium_builder.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 2ce5fc64..98f2ab11 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -118,7 +118,31 @@ static int create_step(size_t data_count, void** data) { return -1; } - // TODO Attempt to build project. + // Attempt to build project. + + LOG_INFO("%s" CLEAR ": Building project in the builder aquarium...", pretty); + + cmd_free(&cmd); + cmd_create(&cmd, "aquarium", "enter", cookie, NULL); + + // clang-format off + cmd_prepare_stdin(&cmd, + "set -e\n" + "export HOME=/root\n" + "export PATH\n" + "cd proj\n" + "bob build\n" + ); + // clang-format on + + cmd_set_redirect(&cmd, false); // It's nice to see these logs. + + if (cmd_exec(&cmd) < 0) { + LOG_FATAL("%s" CLEAR ": Failed to build project in the builder aquarium.", pretty); + return -1; + } + + LOG_SUCCESS("%s" CLEAR ": Built project in the builder aquarium.", pretty); } return 0; From 8bc52b21a6f7ef8734ffad04428e61515abd056d Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 19:10:25 +0100 Subject: [PATCH 15/22] AquariumBuilder: Simplify BSS (just cast state, as that's all the information the build step needs) --- src/class/aquarium_builder.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 98f2ab11..c2d6fc5c 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -26,16 +26,11 @@ typedef struct { char* cookie; } state_t; -typedef struct { - state_t* state; -} bss_create_t; - static int create_step(size_t data_count, void** data) { // TODO Parallelize this, but make sure libaquarium works in parallel first. for (size_t i = 0; i < data_count; i++) { - bss_create_t* const bss = data[i]; - state_t* const state = bss->state; + state_t* const state = data[i]; // Generate the cookie. @@ -217,18 +212,12 @@ static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { inst->inst.free_data = free_state; // Add build step to create aquarium itself. - - bss_create_t* const bss = malloc(sizeof *bss); - assert(bss != NULL); - - bss->state = state; - // We can always do this in parallel as we can have no dependencies, so let's always merge these build steps. // TODO In fact, I'm pretty sure we can run all the AquariumBuilder creation build steps program-wide. We don't need other build steps to fence this. // TODO Is libaquarium happy with this? Either way it's its problem if it breaks in this situation. // We also don't check for frugality here, because the project might have changes, so it'll always have to be rebuilt. - return add_build_step(MAGIC, "Creating aquarium for aquarium builder", create_step, bss); + return add_build_step(MAGIC, "Creating aquarium for aquarium builder", create_step, state); } bob_class_t BOB_CLASS_AQUARIUM_BUILDER = { From 9dec49445124000634d1c94df5cc2343dac57ba7 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 19:24:16 +0100 Subject: [PATCH 16/22] Aquarium: Aquarium creation (kernel included) --- src/class/aquarium.c | 54 ++++++++++++++++++++++++++++++++++-- src/class/aquarium_builder.c | 6 ++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/class/aquarium.c b/src/class/aquarium.c index 22b0a557..59390223 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -3,19 +3,69 @@ #include +#include #include +#include +#include #include #include #include +#include #define AQUARIUM "Aquarium" +#define MAGIC (strhash(AQUARIUM "() magic")) typedef struct { char* template; char* kernel; } state_t; +static size_t aquarium_count = 0; + +static int create_step(size_t data_count, void** data) { + for (size_t i = 0; i < data_count; i++) { + state_t* const state = data[i]; + size_t id = aquarium_count++; + + // Generate the cookie. + + char* STR_CLEANUP cookie = NULL; + asprintf(&cookie, "%s/bob/aquarium.cookie.%zu.aquarium", out_path, id); + assert(cookie != NULL); + + // Create the aquarium. + + LOG_INFO("%s" CLEAR ": Creating aquarium...", state->template); + + cmd_t CMD_CLEANUP cmd = {0}; + cmd_create(&cmd, "aquarium", "-t", state->template, NULL); + + if (state->kernel != NULL) { + cmd_add(&cmd, "-k"); + cmd_add(&cmd, state->kernel); + } + + cmd_add(&cmd, "create"); + cmd_add(&cmd, cookie); + + cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. + int rv = cmd_exec(&cmd); + + if (rv == 0) { + set_owner(cookie); + } + + cmd_log(&cmd, cookie, state->template, "create aquarium", "created aquarium", true); + + if (rv != 0) { + return -1; + } + } + + return 0; +} + static int add_kernel(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 1); @@ -85,9 +135,9 @@ static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { inst->inst.data = state; inst->inst.free_data = free_state; - // TODO Add a build step to create aquarium itself. + // Add build step to create aquarium itself. - return 0; + return add_build_step(MAGIC, "Creating aquarium", create_step, state); } bob_class_t BOB_CLASS_AQUARIUM = { diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index c2d6fc5c..7e25c9c2 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -14,7 +14,7 @@ #include #define AQUARIUM_BUILDER "AquariumBuilder" -#define MAGIC (strhash("AquariumBuilder() magic")) +#define MAGIC (strhash(AQUARIUM_BUILDER "() magic")) typedef struct { char* template; @@ -52,7 +52,7 @@ static int create_step(size_t data_count, void** data) { } else { - LOG_INFO("%s" CLEAR ": Creating builder aquarium, as it doesn't yet exist (this might take several minutes)...", pretty); + LOG_INFO("%s" CLEAR ": Creating builder aquarium, as it doesn't yet exist...", pretty); cmd_create(&cmd, "aquarium", "-t", state->template, "create", cookie, NULL); cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. @@ -62,7 +62,7 @@ static int create_step(size_t data_count, void** data) { set_owner(cookie); } - cmd_log(&cmd, cookie, pretty, "create builder aquarium", "created build aquarium", true); + cmd_log(&cmd, cookie, pretty, "create builder aquarium", "created builder aquarium", true); if (rv != 0) { return -1; From fe2e57179f7a2b58a012c4a35cb271c28252ca87 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 20:51:50 +0100 Subject: [PATCH 17/22] AquariumBuilder: Installing to another aquarium --- src/class/aquarium.c | 40 ++++++++------- src/class/aquarium_builder.c | 99 ++++++++++++++++++++++++++++++------ src/class/aquarium_common.h | 15 ++++++ 3 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 src/class/aquarium_common.h diff --git a/src/class/aquarium.c b/src/class/aquarium.c index 59390223..ecc8952c 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -13,26 +13,28 @@ #include #include +#include "aquarium_common.h" + #define AQUARIUM "Aquarium" #define MAGIC (strhash(AQUARIUM "() magic")) -typedef struct { - char* template; - char* kernel; -} state_t; - static size_t aquarium_count = 0; static int create_step(size_t data_count, void** data) { for (size_t i = 0; i < data_count; i++) { - state_t* const state = data[i]; + aquarium_state_t* const state = data[i]; size_t id = aquarium_count++; // Generate the cookie. - char* STR_CLEANUP cookie = NULL; - asprintf(&cookie, "%s/bob/aquarium.cookie.%zu.aquarium", out_path, id); - assert(cookie != NULL); + asprintf(&state->cookie, "%s/bob/aquarium.cookie.%zu.aquarium", out_path, id); + assert(state->cookie != NULL); + + // If the cookie already exists, remove it. + + if (access(state->cookie, F_OK) == 0) { + remove(state->cookie); + } // Create the aquarium. @@ -47,18 +49,18 @@ static int create_step(size_t data_count, void** data) { } cmd_add(&cmd, "create"); - cmd_add(&cmd, cookie); + cmd_add(&cmd, state->cookie); cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. - int rv = cmd_exec(&cmd); + int const rv = cmd_exec(&cmd); if (rv == 0) { - set_owner(cookie); + set_owner(state->cookie); } - cmd_log(&cmd, cookie, state->template, "create aquarium", "created aquarium", true); + cmd_log(&cmd, NULL, state->template, "create aquarium", "created aquarium", true); - if (rv != 0) { + if (rv < 0) { return -1; } } @@ -66,7 +68,7 @@ static int create_step(size_t data_count, void** data) { return 0; } -static int add_kernel(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { +static int add_kernel(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 1); if (args->args[0]->kind != FLAMINGO_VAL_KIND_STR) { @@ -80,7 +82,7 @@ static int add_kernel(state_t* state, flamingo_arg_list_t* args, flamingo_val_t* return 0; } -static int prep_image(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { +static int prep_image(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 0); // TODO Imaging build step. @@ -91,7 +93,7 @@ static int prep_image(state_t* state, flamingo_arg_list_t* args, flamingo_val_t* static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_val_t** rv, bool* consumed) { *consumed = true; - state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? + aquarium_state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? if (flamingo_cstrcmp(callable->name, "add_kernel", callable->name_size) == 0) { return add_kernel(state, args, rv); @@ -106,7 +108,7 @@ static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_va } static void free_state(flamingo_val_t* inst, void* data) { - state_t* const state = data; + aquarium_state_t* const state = data; free(state->template); free(state->kernel); @@ -124,7 +126,7 @@ static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { // Create state object. - state_t* const state = malloc(sizeof *state); + aquarium_state_t* const state = malloc(sizeof *state); assert(state != NULL); state->template = strndup(args->args[0]->str.str, args->args[0]->str.size); diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 7e25c9c2..66dcb831 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -13,6 +13,8 @@ #include #include +#include "aquarium_common.h" + #define AQUARIUM_BUILDER "AquariumBuilder" #define MAGIC (strhash(AQUARIUM_BUILDER "() magic")) @@ -26,6 +28,11 @@ typedef struct { char* cookie; } state_t; +typedef struct { + state_t* state; + aquarium_state_t* target; +} install_to_bss_t; + static int create_step(size_t data_count, void** data) { // TODO Parallelize this, but make sure libaquarium works in parallel first. @@ -35,9 +42,8 @@ static int create_step(size_t data_count, void** data) { // Generate the cookie. uint64_t const hash = strhash(state->template) ^ strhash(state->project_path); - char* STR_CLEANUP cookie = NULL; - asprintf(&cookie, "%s/bob/aquarium_builder.cookie.%" PRIx64 ".aquarium", out_path, hash); - assert(cookie != NULL); + asprintf(&state->cookie, "%s/bob/aquarium_builder.cookie.%" PRIx64 ".aquarium", out_path, hash); + assert(state->cookie != NULL); // If it doesn't yet exist, create the aquarium. @@ -47,24 +53,24 @@ static int create_step(size_t data_count, void** data) { cmd_t CMD_CLEANUP cmd = {0}; - if (access(cookie, F_OK) == 0) { - log_already_done(cookie, pretty, "created builder aquarium"); + if (access(state->cookie, F_OK) == 0) { + log_already_done(NULL, pretty, "created builder aquarium"); } else { LOG_INFO("%s" CLEAR ": Creating builder aquarium, as it doesn't yet exist...", pretty); - cmd_create(&cmd, "aquarium", "-t", state->template, "create", cookie, NULL); + cmd_create(&cmd, "aquarium", "-t", state->template, "create", state->cookie, NULL); cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. int rv = cmd_exec(&cmd); if (rv == 0) { - set_owner(cookie); + set_owner(state->cookie); } - cmd_log(&cmd, cookie, pretty, "create builder aquarium", "created builder aquarium", true); + cmd_log(&cmd, NULL, pretty, "create builder aquarium", "created builder aquarium", true); - if (rv != 0) { + if (rv < 0) { return -1; } @@ -73,7 +79,7 @@ static int create_step(size_t data_count, void** data) { LOG_INFO("%s" CLEAR ": Installing Bob to the builder aquarium...", pretty); cmd_free(&cmd); - cmd_create(&cmd, "aquarium", "-t", state->template, "enter", cookie, NULL); + cmd_create(&cmd, "aquarium", "-t", state->template, "enter", state->cookie, NULL); // clang-format off cmd_prepare_stdin(&cmd, @@ -92,7 +98,7 @@ static int create_step(size_t data_count, void** data) { if (cmd_exec(&cmd) < 0) { LOG_FATAL("%s" CLEAR ": Failed to install Bob to the builder aquarium.", pretty); - rm(cookie, NULL); // To make sure that we go through this again next time the command is run. + rm(state->cookie, NULL); // To make sure that we go through this again next time the command is run. return -1; } @@ -105,9 +111,9 @@ static int create_step(size_t data_count, void** data) { LOG_INFO("%s" CLEAR ": Copying project to the builder aquarium...", pretty); - cmd_create(&cmd, "aquarium", "cp", cookie, state->project_path, "/proj", NULL); + cmd_create(&cmd, "aquarium", "cp", state->cookie, state->project_path, "/proj", NULL); int const rv = cmd_exec(&cmd); - cmd_log(&cmd, cookie, pretty, "copy project to builder aquarium", "copied project to builder aquarium", false); + cmd_log(&cmd, NULL, pretty, "copy project to builder aquarium", "copied project to builder aquarium", false); if (rv < 0) { return -1; @@ -118,7 +124,7 @@ static int create_step(size_t data_count, void** data) { LOG_INFO("%s" CLEAR ": Building project in the builder aquarium...", pretty); cmd_free(&cmd); - cmd_create(&cmd, "aquarium", "enter", cookie, NULL); + cmd_create(&cmd, "aquarium", "enter", state->cookie, NULL); // clang-format off cmd_prepare_stdin(&cmd, @@ -143,6 +149,58 @@ static int create_step(size_t data_count, void** data) { return 0; } +#define INSTALL_TO_MOUNTPOINT "/mnt" + +static int install_to_step(size_t data_count, void** data) { + for (size_t i = 0; i < data_count; i++) { + install_to_bss_t* const bss = data[i]; + + // Bind mount the target aquarium to the builder. + + LOG_INFO("%s" CLEAR ": Bind mounting target aquarium to builder...", bss->state->template); + + cmd_t CMD_CLEANUP cmd = {0}; + cmd_create(&cmd, "aquarium", "mount", bss->target->cookie, bss->state->cookie, INSTALL_TO_MOUNTPOINT, NULL); + + int const rv = cmd_exec(&cmd); + cmd_log(&cmd, NULL, bss->state->template, "bind mounted target aquarium to builder", "bind mounted target aquarium to builder", false); + + if (rv < 0) { + return -1; + } + + // Actually install the project to the target aquarium. + + LOG_INFO("%s" CLEAR ": Installing built project to target aquarium...", bss->state->template); + + cmd_free(&cmd); + cmd_create(&cmd, "aquarium", "enter", bss->state->cookie, NULL); + + // clang-format off + cmd_prepare_stdin(&cmd, + "set -e\n" + "export HOME=/root\n" + "export PATH\n" + "cd proj\n" + "bob -p " INSTALL_TO_MOUNTPOINT "/usr/local install\n" + ); + // clang-format on + + cmd_set_redirect(&cmd, false); // It's nice to see these logs. + + if (cmd_exec(&cmd) < 0) { + LOG_FATAL("%s" CLEAR ": Failed to install built project to target aquarium.", bss->state->template); + return -1; + } + + LOG_SUCCESS("%s" CLEAR ": Installed built project to target aquarium.", bss->state->template); + + // TODO Unmount? + } + + return 0; +} + static int prep_install_to(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 1); flamingo_val_t* const arg = args->args[0]; @@ -157,9 +215,17 @@ static int prep_install_to(state_t* state, flamingo_arg_list_t* args, flamingo_v return -1; } - // TODO Installing build step. + aquarium_state_t* const target = arg->inst.data; - return 0; + // Add build step to install to target aquarium. + + install_to_bss_t* const bss = malloc(sizeof *bss); + assert(bss != NULL); + + bss->state = state; + bss->target = target; + + return add_build_step(MAGIC ^ strhash(__func__), "Installing built project to target aquarium", install_to_step, bss); } static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_val_t** rv, bool* consumed) { @@ -180,6 +246,7 @@ static void free_state(flamingo_val_t* inst, void* data) { free(state->template); free(state->project_path); + free(state->cookie); free(state); } diff --git a/src/class/aquarium_common.h b/src/class/aquarium_common.h new file mode 100644 index 00000000..644772ec --- /dev/null +++ b/src/class/aquarium_common.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2024 Aymeric Wibo + +#pragma once + +typedef struct { + char* template; + char* kernel; + + // Set but not used by Aquarium. + + char* cookie; +} aquarium_state_t; + +// TODO Put the preliminary aquarium check in here as a simple inline function. From 4988f73ae897a6e1c095f0656ec8cf6ed3d17c1a Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Wed, 25 Dec 2024 22:19:06 +0100 Subject: [PATCH 18/22] Aquarium: Implement `Aquarium.image()` --- src/class/aquarium.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/class/aquarium.c b/src/class/aquarium.c index ecc8952c..62a2ceec 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,13 @@ #define AQUARIUM "Aquarium" #define MAGIC (strhash(AQUARIUM "() magic")) +typedef struct { + aquarium_state_t* state; + char* cookie; +} image_bss_t; + static size_t aquarium_count = 0; +static size_t image_id = 0; static int create_step(size_t data_count, void** data) { for (size_t i = 0; i < data_count; i++) { @@ -68,6 +75,31 @@ static int create_step(size_t data_count, void** data) { return 0; } +static int image_step(size_t data_count, void** data) { + for (size_t i = 0; i < data_count; i++) { + image_bss_t* const bss = data[i]; + + LOG_INFO("%s" CLEAR ": Creating bootable image from aquarium...", bss->state->template); + + cmd_t CMD_CLEANUP cmd = {0}; + cmd_create(&cmd, "aquarium", "image", bss->state->cookie, bss->cookie, NULL); + cmd_set_redirect(&cmd, false); // It's nice to see these logs. + int rv = cmd_exec(&cmd); + + if (rv == 0) { + set_owner(bss->cookie); + } + + cmd_log(&cmd, NULL, bss->state->template, "create bootable image from aquarium", "created bootable image from aquarium", true); + + if (rv < 0) { + return -1; + } + } + + return 0; +} + static int add_kernel(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 1); @@ -85,9 +117,17 @@ static int add_kernel(aquarium_state_t* state, flamingo_arg_list_t* args, flamin static int prep_image(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 0); - // TODO Imaging build step. + image_bss_t* const bss = malloc(sizeof *bss); + assert(bss != NULL); - return 0; + bss->state = state; + + asprintf(&bss->cookie, "%s/bob/aquarium.cookie.%zu.img", out_path, image_id++); + assert(bss->cookie != NULL); + + *rv = flamingo_val_make_cstr(bss->cookie); + + return add_build_step(MAGIC ^ strhash(__func__), "Imaging aquarium", image_step, bss); } static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_val_t** rv, bool* consumed) { From 098b33d3a75d8d87214f920a339b9cc6c149fc80 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Fri, 27 Dec 2024 14:41:49 +0100 Subject: [PATCH 19/22] Aquarium: Add `Aquarium.exec()` --- import/bob.fl | 3 +++ src/class/aquarium.c | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/import/bob.fl b/import/bob.fl index a2874e3c..5ef40731 100644 --- a/import/bob.fl +++ b/import/bob.fl @@ -73,6 +73,9 @@ class Aquarium(template: str) { # Add a kernel template to the aquarium. proto add_kernel(kernel: str) -> void + # Execute an arbitrary command in the aquarium. + proto exec(cmd: str) -> void + # Create a bootable image from the aquarium. # A kernel must have been added to the aquarium with `set_kernel()` before calling this method. proto image -> str diff --git a/src/class/aquarium.c b/src/class/aquarium.c index 62a2ceec..c374a65d 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -19,6 +19,11 @@ #define AQUARIUM "Aquarium" #define MAGIC (strhash(AQUARIUM "() magic")) +typedef struct { + aquarium_state_t* state; + char* cmd; +} exec_bss_t; + typedef struct { aquarium_state_t* state; char* cookie; @@ -75,6 +80,27 @@ static int create_step(size_t data_count, void** data) { return 0; } +static int exec_step(size_t data_count, void** data) { + for (size_t i = 0; i < data_count; i++) { + exec_bss_t* const bss = data[i]; + + LOG_INFO("%s" CLEAR ": Executing command on aquarium...", bss->state->template); + + cmd_t CMD_CLEANUP cmd = {0}; + cmd_create(&cmd, "aquarium", "enter", bss->state->cookie, NULL); + + asprintf(&cmd.pending_stdin, "export HOME=/root\n%s\n", bss->cmd); + assert(cmd.pending_stdin != NULL); + + cmd_set_redirect(&cmd, false); // It's nice to see these logs. + cmd_exec(&cmd); // XXX Return values don't matter to us here, commands can fail. + + cmd_log(&cmd, NULL, bss->state->template, "execute command on aquarium", "executed command on aquarium", true); + } + + return 0; +} + static int image_step(size_t data_count, void** data) { for (size_t i = 0; i < data_count; i++) { image_bss_t* const bss = data[i]; @@ -114,6 +140,31 @@ static int add_kernel(aquarium_state_t* state, flamingo_arg_list_t* args, flamin return 0; } +static size_t exec_count = 0; + +static int prep_exec(aquarium_state_t* state, flamingo_arg_list_t* args) { + assert(args->count == 1); + flamingo_val_t* const arg = args->args[0]; + + if (arg->kind != FLAMINGO_VAL_KIND_STR) { + LOG_FATAL(AQUARIUM ": Expected argument to be string"); + return -1; + } + + char* const cmd = strndup(arg->str.str, arg->str.size); + + // Add build step to execute on the aquarium. + // Order absolutely does matter here. + + exec_bss_t* const bss = malloc(sizeof *bss); + assert(bss != NULL); + + bss->state = state; + bss->cmd = cmd; + + return add_build_step(MAGIC ^ strhash(__func__) + exec_count++, "Executing command on aquarium", exec_step, bss); +} + static int prep_image(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { assert(args->count == 0); @@ -139,6 +190,10 @@ static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_va return add_kernel(state, args, rv); } + else if (flamingo_cstrcmp(callable->name, "exec", callable->name_size) == 0) { + return prep_exec(state, args); + } + else if (flamingo_cstrcmp(callable->name, "image", callable->name_size) == 0) { return prep_image(state, args, rv); } From 889a3fce623506d397fab1ad5ff61facffe4787f Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Fri, 27 Dec 2024 14:42:14 +0100 Subject: [PATCH 20/22] AquariumBuilder: Install to root instead of `/usr/local` --- src/class/aquarium_builder.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 66dcb831..4c9cd232 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -182,7 +182,7 @@ static int install_to_step(size_t data_count, void** data) { "export HOME=/root\n" "export PATH\n" "cd proj\n" - "bob -p " INSTALL_TO_MOUNTPOINT "/usr/local install\n" + "bob -p " INSTALL_TO_MOUNTPOINT " install\n" ); // clang-format on @@ -201,7 +201,7 @@ static int install_to_step(size_t data_count, void** data) { return 0; } -static int prep_install_to(state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { +static int prep_install_to(state_t* state, flamingo_arg_list_t* args) { assert(args->count == 1); flamingo_val_t* const arg = args->args[0]; @@ -234,7 +234,7 @@ static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_va state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? if (flamingo_cstrcmp(callable->name, "install_to", callable->name_size) == 0) { - return prep_install_to(state, args, rv); + return prep_install_to(state, args); } *consumed = false; From fe4c09f5ed7f877a6554373df6dec5231e12b384 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Fri, 27 Dec 2024 17:37:14 +0100 Subject: [PATCH 21/22] AquariumBuilder: Add `Aquarium.add_overlay()` --- import/bob.fl | 3 +++ src/class/aquarium.c | 4 ++-- src/class/aquarium_builder.c | 46 +++++++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/import/bob.fl b/import/bob.fl index 5ef40731..ca9512b1 100644 --- a/import/bob.fl +++ b/import/bob.fl @@ -87,6 +87,9 @@ class Aquarium(template: str) { # The constructor creates a builder aquarium from the template and will have Bob installed to it. # Then, the project at the given path will be copied over to the builder aquarium and be built within that aquarium. class AquariumBuilder(template: str, project_path: str) { + # Add overlay template to the builder aquarium. + proto add_overlay(overlay: str) -> void + # Install the built project to a target aquarium. proto install_to(target: Aquarium) -> void } diff --git a/src/class/aquarium.c b/src/class/aquarium.c index c374a65d..6f1ca6c5 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -126,7 +126,7 @@ static int image_step(size_t data_count, void** data) { return 0; } -static int add_kernel(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) { +static int add_kernel(aquarium_state_t* state, flamingo_arg_list_t* args) { assert(args->count == 1); if (args->args[0]->kind != FLAMINGO_VAL_KIND_STR) { @@ -187,7 +187,7 @@ static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_va aquarium_state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? if (flamingo_cstrcmp(callable->name, "add_kernel", callable->name_size) == 0) { - return add_kernel(state, args, rv); + return add_kernel(state, args); } else if (flamingo_cstrcmp(callable->name, "exec", callable->name_size) == 0) { diff --git a/src/class/aquarium_builder.c b/src/class/aquarium_builder.c index 4c9cd232..d1d86f8d 100644 --- a/src/class/aquarium_builder.c +++ b/src/class/aquarium_builder.c @@ -20,6 +20,7 @@ typedef struct { char* template; + char* overlays; char* project_path; // This is the aquarium itself. @@ -41,7 +42,7 @@ static int create_step(size_t data_count, void** data) { // Generate the cookie. - uint64_t const hash = strhash(state->template) ^ strhash(state->project_path); + uint64_t const hash = strhash(state->template) ^ strhash(state->overlays) ^ strhash(state->project_path); asprintf(&state->cookie, "%s/bob/aquarium_builder.cookie.%" PRIx64 ".aquarium", out_path, hash); assert(state->cookie != NULL); @@ -60,7 +61,16 @@ static int create_step(size_t data_count, void** data) { else { LOG_INFO("%s" CLEAR ": Creating builder aquarium, as it doesn't yet exist...", pretty); - cmd_create(&cmd, "aquarium", "-t", state->template, "create", state->cookie, NULL); + cmd_create(&cmd, "aquarium", "-t", state->template, NULL); + + if (state->overlays != NULL) { + cmd_add(&cmd, "-O"); + cmd_add(&cmd, state->overlays); + } + + cmd_add(&cmd, "create"); + cmd_add(&cmd, state->cookie); + cmd_set_redirect(&cmd, false); // So we can get progress if a template needs to be downloaded e.g. int rv = cmd_exec(&cmd); @@ -201,6 +211,29 @@ static int install_to_step(size_t data_count, void** data) { return 0; } +static int add_overlay(state_t* state, flamingo_arg_list_t* args) { + assert(args->count == 1); + + if (args->args[0]->kind != FLAMINGO_VAL_KIND_STR) { + LOG_FATAL(AQUARIUM_BUILDER ": Expected argument to be a string"); + return -1; + } + + bool const comma = strlen(state->overlays) > 0; + + size_t const prev_len = strlen(state->overlays); + size_t const next_len = args->args[0]->str.size; + + state->overlays = realloc(state->overlays, prev_len + next_len + 1 + comma); + assert(state->overlays != NULL); + + state->overlays[prev_len] = ','; + state->overlays[prev_len + comma + next_len] = '\0'; + memcpy(state->overlays + prev_len + comma, args->args[0]->str.str, next_len); + + return 0; +} + static int prep_install_to(state_t* state, flamingo_arg_list_t* args) { assert(args->count == 1); flamingo_val_t* const arg = args->args[0]; @@ -233,7 +266,11 @@ static int call(flamingo_val_t* callable, flamingo_arg_list_t* args, flamingo_va state_t* const state = callable->owner->owner->inst.data; // TODO Should this be passed to the call function of a class? - if (flamingo_cstrcmp(callable->name, "install_to", callable->name_size) == 0) { + if (flamingo_cstrcmp(callable->name, "add_overlay", callable->name_size) == 0) { + return add_overlay(state, args); + } + + else if (flamingo_cstrcmp(callable->name, "install_to", callable->name_size) == 0) { return prep_install_to(state, args); } @@ -275,6 +312,9 @@ static int instantiate(flamingo_val_t* inst, flamingo_arg_list_t* args) { state->project_path = strndup(args->args[1]->str.str, args->args[1]->str.size); assert(state->project_path != NULL); + state->overlays = strdup(""); + assert(state->overlays != NULL); + inst->inst.data = state; inst->inst.free_data = free_state; From 33bbee73059763db0f8f8cae088ce36f2e3c9af7 Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Fri, 27 Dec 2024 17:39:34 +0100 Subject: [PATCH 22/22] Aquarium: Fix formatting suggestion by GCC --- src/class/aquarium.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class/aquarium.c b/src/class/aquarium.c index 6f1ca6c5..70ce9100 100644 --- a/src/class/aquarium.c +++ b/src/class/aquarium.c @@ -162,7 +162,7 @@ static int prep_exec(aquarium_state_t* state, flamingo_arg_list_t* args) { bss->state = state; bss->cmd = cmd; - return add_build_step(MAGIC ^ strhash(__func__) + exec_count++, "Executing command on aquarium", exec_step, bss); + return add_build_step((MAGIC ^ strhash(__func__)) + exec_count++, "Executing command on aquarium", exec_step, bss); } static int prep_image(aquarium_state_t* state, flamingo_arg_list_t* args, flamingo_val_t** rv) {