diff options
author | Joseph Hunkeler <jhunkeler@gmail.com> | 2024-10-14 09:32:03 -0400 |
---|---|---|
committer | Joseph Hunkeler <jhunkeler@gmail.com> | 2024-10-14 09:43:31 -0400 |
commit | 5a9688e9e78a25a42bddfc4388fb4ce3311ded74 (patch) | |
tree | bcc1b54c3f8a7f1eab0d6b3e129f098721a41537 /src/conda.c | |
parent | b98088f7b7cfe4b08eb39fa1b6b86210cb6c08b8 (diff) | |
download | stasis-5a9688e9e78a25a42bddfc4388fb4ce3311ded74.tar.gz |
Refactor directory structure
* Move core library sources into src/lib/core
* Move command-line programs into src/cli
Diffstat (limited to 'src/conda.c')
-rw-r--r-- | src/conda.c | 466 |
1 files changed, 0 insertions, 466 deletions
diff --git a/src/conda.c b/src/conda.c deleted file mode 100644 index e60abc7..0000000 --- a/src/conda.c +++ /dev/null @@ -1,466 +0,0 @@ -// -// Created by jhunk on 5/14/23. -// - -#include <unistd.h> -#include "conda.h" - -int micromamba(struct MicromambaInfo *info, char *command, ...) { - struct utsname sys; - uname(&sys); - - tolower_s(sys.sysname); - if (!strcmp(sys.sysname, "darwin")) { - strcpy(sys.sysname, "osx"); - } - - if (!strcmp(sys.machine, "x86_64")) { - strcpy(sys.machine, "64"); - } - - char url[PATH_MAX]; - sprintf(url, "https://micro.mamba.pm/api/micromamba/%s-%s/latest", sys.sysname, sys.machine); - - char installer_path[PATH_MAX]; - sprintf(installer_path, "%s/latest", getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"); - - if (access(installer_path, F_OK)) { - download(url, installer_path, NULL); - } - - char mmbin[PATH_MAX]; - sprintf(mmbin, "%s/micromamba", info->micromamba_prefix); - - if (access(mmbin, F_OK)) { - char untarcmd[PATH_MAX * 2]; - mkdirs(info->micromamba_prefix, 0755); - sprintf(untarcmd, "tar -xvf %s -C %s --strip-components=1 bin/micromamba 1>/dev/null", installer_path, info->micromamba_prefix); - int untarcmd_status = system(untarcmd); - if (untarcmd_status) { - return -1; - } - } - - char cmd[STASIS_BUFSIZ]; - memset(cmd, 0, sizeof(cmd)); - sprintf(cmd, "%s -r %s -p %s ", mmbin, info->conda_prefix, info->conda_prefix); - va_list args; - va_start(args, command); - vsprintf(cmd + strlen(cmd), command, args); - va_end(args); - - mkdirs(info->conda_prefix, 0755); - - char rcpath[PATH_MAX]; - sprintf(rcpath, "%s/.condarc", info->conda_prefix); - touch(rcpath); - - setenv("CONDARC", rcpath, 1); - setenv("MAMBA_ROOT_PREFIX", info->conda_prefix, 1); - int status = system(cmd); - unsetenv("MAMBA_ROOT_PREFIX"); - - return status; -} - -int python_exec(const char *args) { - char command[PATH_MAX]; - memset(command, 0, sizeof(command)); - snprintf(command, sizeof(command) - 1, "python %s", args); - msg(STASIS_MSG_L3, "Executing: %s\n", command); - return system(command); -} - -int pip_exec(const char *args) { - char command[PATH_MAX]; - memset(command, 0, sizeof(command)); - snprintf(command, sizeof(command) - 1, "python -m pip %s", args); - msg(STASIS_MSG_L3, "Executing: %s\n", command); - return system(command); -} - -int pip_index_provides(const char *index_url, const char *spec) { - char cmd[PATH_MAX] = {0}; - char spec_local[255] = {0}; - - if (isempty((char *) spec)) { - // NULL or zero-length; no package spec means there's nothing to do. - return -1; - } - - // Normalize the local spec string - strncpy(spec_local, spec, sizeof(spec_local) - 1); - tolower_s(spec_local); - lstrip(spec_local); - strip(spec_local); - - char logfile[] = "/tmp/STASIS-package_exists.XXXXXX"; - int logfd = mkstemp(logfile); - if (logfd < 0) { - perror(logfile); - remove(logfile); // fail harmlessly if not present - return -1; - } - - - int status = 0; - struct Process proc; - memset(&proc, 0, sizeof(proc)); - proc.redirect_stderr = 1; - strcpy(proc.f_stdout, logfile); - - // Do an installation in dry-run mode to see if the package exists in the given index. - snprintf(cmd, sizeof(cmd) - 1, "python -m pip install --dry-run --no-deps --index-url=%s %s", index_url, spec_local); - status = shell(&proc, cmd); - - // Print errors only when shell() itself throws one - // If some day we want to see the errors thrown by pip too, use this condition instead: (status != 0) - if (status < 0) { - FILE *fp = fdopen(logfd, "r"); - if (!fp) { - remove(logfile); - return -1; - } else { - char line[BUFSIZ] = {0}; - fflush(stdout); - fflush(stderr); - while (fgets(line, sizeof(line) - 1, fp) != NULL) { - fprintf(stderr, "%s", line); - } - fflush(stderr); - fclose(fp); - } - } - remove(logfile); - return proc.returncode == 0; -} - -int conda_exec(const char *args) { - char command[PATH_MAX]; - const char *mamba_commands[] = { - "build", - "install", - "update", - "create", - "list", - "search", - "run", - "info", - "clean", - "activate", - "deactivate", - NULL - }; - char conda_as[6]; - memset(conda_as, 0, sizeof(conda_as)); - - strcpy(conda_as, "conda"); - for (size_t i = 0; mamba_commands[i] != NULL; i++) { - if (startswith(args, mamba_commands[i])) { - strcpy(conda_as, "mamba"); - break; - } - } - - snprintf(command, sizeof(command) - 1, "%s %s", conda_as, args); - msg(STASIS_MSG_L3, "Executing: %s\n", command); - return system(command); -} - -int conda_activate(const char *root, const char *env_name) { - int fd = -1; - FILE *fp = NULL; - const char *init_script_conda = "/etc/profile.d/conda.sh"; - const char *init_script_mamba = "/etc/profile.d/mamba.sh"; - char path_conda[PATH_MAX] = {0}; - char path_mamba[PATH_MAX] = {0}; - char logfile[PATH_MAX] = {0}; - struct Process proc; - memset(&proc, 0, sizeof(proc)); - - // Where to find conda's init scripts - sprintf(path_conda, "%s%s", root, init_script_conda); - sprintf(path_mamba, "%s%s", root, init_script_mamba); - - // Set the path to our stdout log - // Emulate mktemp()'s behavior. Give us a unique file name, but don't use - // the file handle at all. We'll open it as a FILE stream soon enough. - sprintf(logfile, "%s/%s", globals.tmpdir, "shell_XXXXXX"); - fd = mkstemp(logfile); - if (fd < 0) { - perror(logfile); - return -1; - } - close(fd); - - // Configure our process for output to a log file - strcpy(proc.f_stdout, logfile); - - // Verify conda's init scripts are available - if (access(path_conda, F_OK) < 0) { - perror(path_conda); - remove(logfile); - return -1; - } - - if (access(path_mamba, F_OK) < 0) { - perror(path_mamba); - remove(logfile); - return -1; - } - - // Fully activate conda and record its effect on the runtime environment - char command[PATH_MAX * 3]; - snprintf(command, sizeof(command) - 1, "set -a; source %s; source %s; conda activate %s &>/dev/null; env -0", path_conda, path_mamba, env_name); - int retval = shell(&proc, command); - if (retval) { - // it didn't work; drop out for cleanup - remove(logfile); - return retval; - } - - // Parse the log file: - // 1. Extract the environment keys and values from the sub-shell - // 2. Apply it to STASIS's runtime environment - // 3. Now we're ready to execute conda commands anywhere - fp = fopen(proc.f_stdout, "r"); - if (!fp) { - perror(logfile); - return -1; - } - - while (!feof(fp)) { - char buf[STASIS_BUFSIZ] = {0}; - int ch = 0; - size_t z = 0; - // We are ingesting output from "env -0" and can't use fgets() - // Copy each character into the buffer until we encounter '\0' or EOF - while (z < sizeof(buf) && (ch = (int) fgetc(fp)) != 0) { - if (ch == EOF) { - break; - } - buf[z] = (char) ch; - z++; - } - buf[strlen(buf)] = 0; - - if (!strlen(buf)) { - continue; - } - - char **part = split(buf, "=", 1); - if (!part) { - perror("unable to split environment variable buffer"); - return -1; - } - if (!part[0]) { - msg(STASIS_MSG_WARN | STASIS_MSG_L1, "Invalid environment variable key ignored: '%s'\n", buf); - } else if (!part[1]) { - msg(STASIS_MSG_WARN | STASIS_MSG_L1, "Invalid environment variable value ignored: '%s'\n", buf); - } else { - setenv(part[0], part[1], 1); - } - GENERIC_ARRAY_FREE(part); - } - fclose(fp); - remove(logfile); - return 0; -} - -int conda_check_required() { - int status = 0; - struct StrList *result = NULL; - char cmd[PATH_MAX] = {0}; - const char *conda_minimum_viable_tools[] = { - "boa", - "conda-build", - "conda-verify", - NULL - }; - - // Construct a "conda list" command that searches for all required packages - // using conda's (python's) regex matching - strcat(cmd, "conda list '"); - for (size_t i = 0; conda_minimum_viable_tools[i] != NULL; i++) { - strcat(cmd, "^"); - strcat(cmd, conda_minimum_viable_tools[i]); - if (conda_minimum_viable_tools[i + 1] != NULL) { - strcat(cmd, "|"); - } - } - strcat(cmd, "' | cut -d ' ' -f 1"); - - // Verify all required packages are installed - char *cmd_out = shell_output(cmd, &status); - if (cmd_out) { - size_t found = 0; - result = strlist_init(); - strlist_append_tokenize(result, cmd_out, "\n"); - for (size_t i = 0; i < strlist_count(result); i++) { - char *item = strlist_item(result, i); - if (isempty(item) || startswith(item, "#")) { - continue; - } - - for (size_t x = 0; conda_minimum_viable_tools[x] != NULL; x++) { - if (!strcmp(item, conda_minimum_viable_tools[x])) { - found++; - } - } - } - if (found < (sizeof(conda_minimum_viable_tools) / sizeof(*conda_minimum_viable_tools)) - 1) { - guard_free(cmd_out); - guard_strlist_free(&result); - return 1; - } - guard_free(cmd_out); - guard_strlist_free(&result); - } else { - msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "The base package requirement check could not be performed\n"); - return 2; - } - return 0; -} - -int conda_setup_headless() { - if (globals.verbose) { - conda_exec("config --system --set quiet false"); - } else { - // Not verbose, so squelch conda's noise - conda_exec("config --system --set quiet true"); - } - - // Configure conda for headless CI - conda_exec("config --system --set auto_update_conda false"); // never update conda automatically - conda_exec("config --system --set notify_outdated_conda false"); // never notify about outdated conda version - conda_exec("config --system --set always_yes true"); // never prompt for input - conda_exec("config --system --set safety_checks disabled"); // speedup - conda_exec("config --system --set rollback_enabled false"); // speedup - conda_exec("config --system --set report_errors false"); // disable data sharing - conda_exec("config --system --set solver libmamba"); // use a real solver - - char cmd[PATH_MAX]; - size_t total = 0; - if (globals.conda_packages && strlist_count(globals.conda_packages)) { - memset(cmd, 0, sizeof(cmd)); - strcpy(cmd, "install "); - - total = strlist_count(globals.conda_packages); - for (size_t i = 0; i < total; i++) { - char *item = strlist_item(globals.conda_packages, i); - if (isempty(item)) { - continue; - } - sprintf(cmd + strlen(cmd), "'%s'", item); - if (i < total - 1) { - strcat(cmd, " "); - } - } - - if (conda_exec(cmd)) { - msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Unable to install user-defined base packages (conda)\n"); - return 1; - } - } - - if (globals.pip_packages && strlist_count(globals.pip_packages)) { - memset(cmd, 0, sizeof(cmd)); - strcpy(cmd, "install "); - - total = strlist_count(globals.pip_packages); - for (size_t i = 0; i < total; i++) { - char *item = strlist_item(globals.pip_packages, i); - if (isempty(item)) { - continue; - } - sprintf(cmd + strlen(cmd), "'%s'", item); - if (i < total - 1) { - strcat(cmd, " "); - } - } - - if (pip_exec(cmd)) { - msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Unable to install user-defined base packages (pip)\n"); - return 1; - } - } - - if (conda_check_required()) { - msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Your STASIS configuration lacks the bare" - " minimum software required to build conda packages." - " Please fix it.\n"); - return 1; - } - - if (globals.always_update_base_environment) { - if (conda_exec("update --all")) { - fprintf(stderr, "conda update was unsuccessful\n"); - return 1; - } - } - - return 0; -} - -int conda_env_create_from_uri(char *name, char *uri) { - char env_command[PATH_MAX]; - sprintf(env_command, "env create -n %s -f %s", name, uri); - return conda_exec(env_command); -} - -int conda_env_create(char *name, char *python_version, char *packages) { - char env_command[PATH_MAX]; - sprintf(env_command, "create -n %s python=%s %s", name, python_version, packages ? packages : ""); - return conda_exec(env_command); -} - -int conda_env_remove(char *name) { - char env_command[PATH_MAX]; - sprintf(env_command, "env remove -n %s", name); - return conda_exec(env_command); -} - -int conda_env_export(char *name, char *output_dir, char *output_filename) { - char env_command[PATH_MAX]; - sprintf(env_command, "env export -n %s -f %s/%s.yml", name, output_dir, output_filename); - return conda_exec(env_command); -} - -char *conda_get_active_environment() { - const char *name = getenv("CONDA_DEFAULT_ENV"); - if (!name) { - return NULL; - } - - char *result = NULL; - result = strdup(name); - if (!result) { - return NULL; - } - - return result; -} - -int conda_provides(const char *spec) { - struct Process proc; - memset(&proc, 0, sizeof(proc)); - strcpy(proc.f_stdout, "/dev/null"); - strcpy(proc.f_stderr, "/dev/null"); - - // It's worth noting the departure from using conda_exec() here: - // conda_exec() expects the program output to be visible to the user. - // For this operation we only need the exit value. - char cmd[PATH_MAX] = {0}; - snprintf(cmd, sizeof(cmd) - 1, "mamba search --use-index-cache %s", spec); - if (shell(&proc, cmd) < 0) { - fprintf(stderr, "shell: %s", strerror(errno)); - return -1; - } - return proc.returncode == 0; -} - -int conda_index(const char *path) { - char command[PATH_MAX]; - sprintf(command, "index %s", path); - return conda_exec(command); -} |