diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..c33b351 --- /dev/null +++ b/src/main.c @@ -0,0 +1,373 @@ +#define GNU_SOURCE 1 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <time.h> +#include <sys/utsname.h> +#include "ohmycal.h" +#include "wheel.h" + +const char *VERSION = "1.0.0"; +const char *AUTHOR = "Joseph Hunkeler"; +const char *BANNER = "---------------------------------------------------------------------\n" + " ██████╗ ██╗ ██╗ ███╗ ███╗██╗ ██╗ ██████╗ █████╗ ██╗ \n" + "██╔═══██╗██║ ██║ ████╗ ████║╚██╗ ██╔╝ ██╔════╝██╔══██╗██║ \n" + "██║ ██║███████║ ██╔████╔██║ ╚████╔╝ ██║ ███████║██║ \n" + "██║ ██║██╔══██║ ██║╚██╔╝██║ ╚██╔╝ ██║ ██╔══██║██║ \n" + "╚██████╔╝██║ ██║ ██║ ╚═╝ ██║ ██║ ╚██████╗██║ ██║███████╗\n" + " ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝\n" + "---------------------------------------------------------------------\n" + " Delivery Generator \n" + " v%s\n" + "---------------------------------------------------------------------\n" + "Copyright (C) 2023 %s,\n" + "Association of Universities for Research in Astronomy (AURA)\n"; + + +void conda_setup_headless() { + // Configure conda for headless CI + conda_exec("config --system --set auto_update_conda false"); + conda_exec("config --system --set always_yes true"); + conda_exec("config --system --set quiet true"); + conda_exec("config --system --set rollback_enabled false"); + conda_exec("config --system --set report_errors false"); + + if (conda_exec("update --all")) { + perror("update base"); + exit(1); + } +} + +void delivery_install_conda(char *install_script, char *conda_install_dir) { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + + if (!access(conda_install_dir, F_OK)) { + if (rmtree(conda_install_dir)) { + perror("unable to remove previous installation"); + exit(1); + } + } + + // -b = batch mode + if (shell_safe(&proc, (char *[]) {find_program("bash"), install_script, "-b", "-p", conda_install_dir, NULL})) { + fprintf(stderr, "conda installation failed\n"); + exit(1); + } +} + +void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir) { + if (conda_activate(conda_install_dir, "base")) { + fprintf(stderr, "conda activation failed\n"); + exit(1); + } + + if (runtime_replace(&ctx->runtime.environ, __environ)) { + perror("unable to replace runtime environment after activating conda"); + exit(1); + } + + conda_setup_headless(); +} + +#define DEFER_CONDA 0 +#define DEFER_PIP 1 +void delivery_defer_packages(struct Delivery *ctx, int type) { + struct StrList *dataptr = NULL; + struct StrList *deferred = NULL; + char *name = NULL; + char cmd[PATH_MAX]; + + memset(cmd, 0, sizeof(cmd)); + + char mode[10]; + if (DEFER_CONDA == type) { + dataptr = ctx->conda.conda_packages; + deferred = ctx->conda.conda_packages_defer; + strcpy(mode, "conda"); + } else if (DEFER_PIP == type) { + dataptr = ctx->conda.pip_packages; + deferred = ctx->conda.pip_packages_defer; + strcpy(mode, "pip"); + } + msg(OMC_MSG_L2, "Filtering %s packages by test definition...\n", mode); + + struct StrList *filtered = NULL; + filtered = strlist_init(); + for (size_t i = 0, z = 0; i < strlist_count(dataptr); i++) { + name = strlist_item(dataptr, i); + if (!strlen(name) || isblank(*name) || isspace(*name)) { + continue; + } + msg(OMC_MSG_L3, "package '%s': ", name); + int ignore_pkg = 0; + for (size_t x = 0; x < sizeof(ctx->tests) / sizeof(ctx->tests[0]); x++) { + if (ctx->tests[x].name) { + if (startswith(ctx->tests[x].name, name)) { + ignore_pkg = 1; + z++; + break; + } + } + } + + if (ignore_pkg) { + printf("BUILD FOR HOST\n"); + strlist_append(deferred, name); + } else { + printf("USE EXISTING\n"); + strlist_append(filtered, name); + } + } + + if (!strlist_count(deferred)) { + msg(OMC_MSG_WARN, "No packages were filtered by test definitions"); + } else { + if (DEFER_CONDA == type) { + strlist_free(ctx->conda.conda_packages); + ctx->conda.conda_packages = strlist_copy(filtered); + } else if (DEFER_PIP == type) { + strlist_free(ctx->conda.pip_packages); + ctx->conda.pip_packages = strlist_copy(filtered); + } + } +} + +void testfunc(struct Delivery *ctx, char *env_name) { + struct Wheel *wheel; + wheel = get_wheel_file(ctx->storage.wheel_artifact_dir, "drizzlepac", (char *[]) {"cp310", "x86_64", NULL}); + return; +} + +int main(int argc, char *argv[], char *arge[]) { + struct INIFILE *cfg = NULL; + struct INIFILE *ini = NULL; + struct Delivery ctx; + struct Process proc = { + .stdout = "", + .stderr = "", + .redirect_stderr = 0, + }; + struct tm *tm_info; + time_t timenow; + char env_date[100]; + char env_name[PATH_MAX]; + char env_name_testing[PATH_MAX]; + char env_pyver[10]; + char *delivery_input = argv[1]; + char *config_input = argv[2]; + char installer_url[PATH_MAX]; + + memset(&proc, 0, sizeof(proc)); + memset(&ctx, 0, sizeof(ctx)); + + if (!delivery_input) { + fprintf(stderr, "Missing *.ini file\n"); + exit(1); + } + + msg(OMC_MSG_L1, "Initializing\n"); + struct utsname uts; + uname(&uts); + + msg(OMC_MSG_L2, "Setting architecture\n"); + char archsuffix[255]; + ctx.system.arch = strdup(uts.machine); + if (!strcmp(ctx.system.arch, "x86_64")) { + strcpy(archsuffix, "64"); + } else { + strcpy(archsuffix, ctx.system.arch); + } + + msg(OMC_MSG_L2, "Setting platform\n"); + strcpy(ctx.system.platform[DELIVERY_PLATFORM], uts.sysname); + if (!strcmp(ctx.system.platform[DELIVERY_PLATFORM], "Darwin")) { + sprintf(ctx.system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], "osx-%s", archsuffix); + strcpy(ctx.system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "MacOSX"); + strcpy(ctx.system.platform[DELIVERY_PLATFORM_RELEASE], "macos"); + } else if (!strcmp(ctx.system.platform[DELIVERY_PLATFORM], "Linux")) { + sprintf(ctx.system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], "linux-%s", archsuffix); + strcpy(ctx.system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "Linux"); + strcpy(ctx.system.platform[DELIVERY_PLATFORM_RELEASE], "linux"); + } else { + // Not explicitly supported systems + strcpy(ctx.system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], ctx.system.platform[DELIVERY_PLATFORM]); + strcpy(ctx.system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], ctx.system.platform[DELIVERY_PLATFORM]); + strcpy(ctx.system.platform[DELIVERY_PLATFORM_RELEASE], ctx.system.platform[DELIVERY_PLATFORM]); + tolower_s(ctx.system.platform[DELIVERY_PLATFORM_RELEASE]); + } + + msg(OMC_MSG_L2, "Setting up runtime environment...\n"); + setenv("OMC_ARCH", ctx.system.arch, 1); + setenv("OMC_PLATFORM", ctx.system.platform[DELIVERY_PLATFORM], 1); + setenv("OMC_CONDA_ARCH", ctx.system.arch, 1); + setenv("OMC_CONDA_PLATFORM", ctx.system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], 1); + setenv("OMC_CONDA_PLATFORM_SUBDIR", ctx.system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], 1); + + if (config_input) { + msg(OMC_MSG_L2, "Reading OMC global configuration: %s\n", config_input); + cfg = ini_open(config_input); + //ini_show(cfg); + } + + msg(OMC_MSG_L2, "Reading OMC delivery configuration: %s\n", delivery_input); + ini = ini_open(delivery_input); + //ini_show(ini); + + printf(BANNER, VERSION, AUTHOR); + + delivery_init(&ctx, ini, cfg); + runtime_apply(ctx.runtime.environ); + msg(OMC_MSG_L1, "Overview\n"); + delivery_meta_show(&ctx); + delivery_conda_show(&ctx); + delivery_tests_show(&ctx); + + msg(OMC_MSG_L1, "Conda setup\n"); + delivery_get_installer_url(&ctx, installer_url); + msg(OMC_MSG_L2, "Downloading: %s\n", installer_url); + delivery_get_installer(installer_url); + + // Unlikely to occur: this should help prevent rmtree() from destroying your entire filesystem + // if path is "/" then, die + // or if empty string, die + if (!strcmp(ctx.storage.conda_install_prefix, DIR_SEP) || !strlen(ctx.storage.conda_install_prefix)) { + fprintf(stderr, "error: ctx.storage.conda_install_prefix is malformed!\n"); + exit(1); + } + + msg(OMC_MSG_L2, "Installing: %s\n", path_basename(installer_url)); + delivery_install_conda(path_basename(installer_url), ctx.storage.conda_install_prefix); + + msg(OMC_MSG_L2, "Configuring: %s\n", ctx.storage.conda_install_prefix); + delivery_conda_enable(&ctx, ctx.storage.conda_install_prefix); + + // Generate release and/or environment name + memset(env_pyver, 0, sizeof(env_pyver)); + char *tmp_pyver = to_short_version(ctx.meta.python); + sprintf(env_pyver, "py%s", tmp_pyver); + free(tmp_pyver); + tmp_pyver = NULL; + + msg(OMC_MSG_L1, "Generating release string\n"); + if (!strcasecmp(ctx.meta.mission, "hst") && ctx.meta.final) { + memset(env_date, 0, sizeof(env_date)); + strftime(env_date, sizeof(env_date) - 1, "%Y%m%d", tm_info); + sprintf(env_name, "%s_%s_rc%d", ctx.meta.name, env_date, ctx.meta.rc); + } else if (!strcasecmp(ctx.meta.mission, "hst")) { + sprintf(env_name, "%s_%s_%s_%s_rc%d", ctx.meta.name, ctx.meta.codename, ctx.system.platform[DELIVERY_PLATFORM_RELEASE], env_pyver, ctx.meta.rc); + } else if (!strcasecmp(ctx.meta.mission, "jwst") && ctx.meta.final) { + sprintf(env_name, "%s_%s_rc%d", ctx.meta.name, ctx.meta.version, ctx.meta.rc); + } else if (!strcasecmp(ctx.meta.mission, "jwst")) { + sprintf(env_name, "%s_%s_final", ctx.meta.name, ctx.meta.version); + } + msg(OMC_MSG_L2, "%s\n", env_name); + sprintf(env_name_testing, "%s_test", env_name); + + msg(OMC_MSG_L1, "Creating release environment(s)\n"); + if (ctx.meta.based_on && strlen(ctx.meta.based_on)) { + conda_env_remove(env_name); + conda_env_create_from_uri(env_name, ctx.meta.based_on); + + conda_env_remove(env_name_testing); + conda_env_create_from_uri(env_name_testing, ctx.meta.based_on); + } else { + conda_env_create(env_name, ctx.meta.python, NULL); + conda_env_create(env_name_testing, ctx.meta.python, NULL); + } + + // Activate test environment + msg(OMC_MSG_L1, "Activating test environment\n"); + if (conda_activate(ctx.storage.conda_install_prefix, env_name_testing)) { + fprintf(stderr, "failed to activate test environment\n"); + exit(1); + } + + msg(OMC_MSG_L2, "Installing build tools\n"); + if (conda_exec("install boa conda-build conda-verify")) { + msg(OMC_MSG_ERROR | OMC_MSG_L2, "conda-build installation failed"); + exit(1); + } + + if (pip_exec("install build")) { + msg(OMC_MSG_ERROR | OMC_MSG_L2, "'build' tool installation failed"); + exit(1); + } + + time(&timenow); + tm_info = localtime(&timenow); + + // Execute configuration-defined tests + msg(OMC_MSG_L1, "Begin test execution\n"); + delivery_tests_run(&ctx); + + msg(OMC_MSG_L1, "Generating deferred package listing\n"); + // Test succeeded so move on to producing package artifacts + delivery_defer_packages(&ctx, DEFER_CONDA); + delivery_defer_packages(&ctx, DEFER_PIP); + + // TODO: wheels would be nice, but can't right now + //if (ctx.conda.pip_packages_defer) { + // if (!delivery_build_wheels(&ctx)) { + // exit(1); + // } + // if (delivery_copy_wheel_artifacts(&ctx)) { + // exit(1); + // } + // if (delivery_index_wheel_artifacts(&ctx)) { + // exit(1); + // } + //} + + if (ctx.conda.conda_packages_defer) { + msg(OMC_MSG_L2, "Building Conda recipe(s)\n"); + if (delivery_build_recipes(&ctx)) { + exit(1); + } + msg(OMC_MSG_L3, "Copying artifacts\n"); + if (delivery_copy_conda_artifacts(&ctx)) { + exit(1); + } + msg(OMC_MSG_L3, "Indexing artifacts\n"); + if (delivery_index_conda_artifacts(&ctx)) { + exit(1); + } + } + + // Populate the release environment + msg(OMC_MSG_L1, "Populating release environment\n"); + + msg(OMC_MSG_L2, "Installing conda packages\n"); + delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_CONDA, (struct StrList *[]) {ctx.conda.conda_packages, NULL}); + msg(OMC_MSG_L3, "Installing deferred conda packages\n"); + delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_CONDA | INSTALL_PKG_CONDA_DEFERRED, (struct StrList *[]) {ctx.conda.conda_packages_defer, NULL}); + msg(OMC_MSG_L2, "Installing pip packages\n"); + delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_PIP, (struct StrList *[]) {ctx.conda.pip_packages, NULL}); + msg(OMC_MSG_L3, "Installing deferred pip packages\n"); + delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_PIP | INSTALL_PKG_PIP_DEFERRED, (struct StrList *[]) {ctx.conda.pip_packages_defer, NULL}); + + conda_exec("list"); + + msg(OMC_MSG_L1, "Creating release\n"); + msg(OMC_MSG_L2, "Exporting %s\n", env_name_testing); + conda_env_export(env_name_testing, ctx.storage.delivery_dir, env_name_testing); + + msg(OMC_MSG_L2, "Exporting %s\n", env_name); + conda_env_export(env_name, ctx.storage.delivery_dir, env_name); + + // Rewrite release environment output (i.e. set package origin(s) to point to the deployment server, etc.) + char specfile[PATH_MAX]; + sprintf(specfile, "%s/%s.yml", ctx.storage.delivery_dir, env_name); + msg(OMC_MSG_L3, "Rewriting release file %s\n", path_basename(specfile)); + delivery_rewrite_spec(&ctx, specfile); + + msg(OMC_MSG_L1, "Cleaning up\n"); + ini_free(&ini); + ini_free(&cfg); + + msg(OMC_MSG_L1, "Done!\n"); + return 0; +} + |