aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@users.noreply.github.com>2026-06-23 11:00:55 -0400
committerGitHub <noreply@github.com>2026-06-23 11:00:55 -0400
commit5e0b3fbefebaeff1fa5bfb338c760b9b9e270f02 (patch)
tree14b22fc5b82a9db87720c820c54422c9b9c47a1c
parent70c1ba3962166853fc7a1e4f2bb1d637104312b1 (diff)
parent0f6a1c982c2f417e9f0de968bbb7ce58299a8e8d (diff)
downloadstasis-1.8.0.tar.gz
Merge pull request #147 from jhunkeler/conda-updates1.8.0
Support modern versions of conda/mamba
-rw-r--r--README.md1
-rw-r--r--src/cli/stasis/stasis_main.c2
-rw-r--r--src/lib/core/conda.c264
-rw-r--r--src/lib/core/include/conda.h62
-rw-r--r--src/lib/core/include/recipe.h28
-rw-r--r--src/lib/core/include/utils.h2
-rw-r--r--src/lib/core/recipe.c32
-rw-r--r--src/lib/core/system.c4
-rw-r--r--src/lib/core/utils.c80
-rw-r--r--src/lib/delivery/delivery.c43
-rw-r--r--src/lib/delivery/delivery_build.c67
-rw-r--r--src/lib/delivery/delivery_conda.c51
-rw-r--r--src/lib/delivery/delivery_populate.c48
-rw-r--r--src/lib/delivery/include/delivery.h5
-rw-r--r--stasis.ini4
-rw-r--r--tests/data/compression/bz2bin0 -> 42 bytes
-rw-r--r--tests/data/compression/gzbin0 -> 26 bytes
-rw-r--r--tests/data/compression/none1
-rw-r--r--tests/data/compression/xzbin0 -> 72 bytes
-rw-r--r--tests/data/compression/zipbin0 -> 202 bytes
-rw-r--r--tests/data/compression/zstdbin0 -> 19 bytes
-rw-r--r--tests/data/gbo_ng.ini66
-rw-r--r--tests/include/testing.h3
-rw-r--r--tests/rt_generic_ng_based_on.sh30
-rw-r--r--tests/test_conda.c52
-rw-r--r--tests/test_recipe.c12
-rw-r--r--tests/test_str.c57
-rw-r--r--tests/test_utils.c47
-rw-r--r--tests/test_wheel.c6
29 files changed, 746 insertions, 221 deletions
diff --git a/README.md b/README.md
index 76ccf52..caf46d0 100644
--- a/README.md
+++ b/README.md
@@ -207,6 +207,7 @@ stasis mydelivery.ini
| STASIS_DOWNLOAD_TIMEOUT | Number of seconds before timing out a remote file download |
| STASIS_DOWNLOAD_RETRY_MAX | Number of retries before giving up on a remote file download |
| STASIS_DOWNLOAD_RETRY_SECONDS | Number of seconds to wait before retrying a remote file download |
+| STASIS_ALWAYS_BUILD_FOR_HOST | If set, build all software from source (for debugging) |
## Main configuration (stasis.ini)
diff --git a/src/cli/stasis/stasis_main.c b/src/cli/stasis/stasis_main.c
index ef7bf26..c5c1f00 100644
--- a/src/cli/stasis/stasis_main.c
+++ b/src/cli/stasis/stasis_main.c
@@ -176,7 +176,7 @@ static void setup_conda(struct Delivery *ctx, char *installer_url, const size_t
delivery_install_conda(ctx->conda.installer_path, ctx->storage.conda_install_prefix);
msg(STASIS_MSG_L2, "Configuring: %s\n", ctx->storage.conda_install_prefix);
- delivery_conda_enable(ctx, ctx->storage.conda_install_prefix);
+ delivery_conda_enable(ctx);
}
static void configure_conda_base(struct Delivery *ctx, char *envs[]) {
diff --git a/src/lib/core/conda.c b/src/lib/core/conda.c
index 5c7779f..bf27d21 100644
--- a/src/lib/core/conda.c
+++ b/src/lib/core/conda.c
@@ -194,6 +194,31 @@ int pip_exec(const char *args) {
return result;
}
+char *python_importlib_metadata_version(const char *package_name) {
+ int status = 0;
+ char cmd[PATH_MAX] = {0};
+
+ if (strpbrk(package_name, "\\/*{}()|;&\"'\r\n")) {
+ SYSERROR("package name is invalid: '%s'", package_name);
+ return NULL;
+ }
+ snprintf(cmd, sizeof(cmd), "python3 -c 'from importlib.metadata import version; print(version(r\x22%s\x22))'", package_name);
+
+ char *version = shell_output(cmd, &status);
+ if (status) {
+ SYSERROR("version detection failed");
+ guard_free(version);
+ return NULL;
+ }
+ if (!version) {
+ SYSERROR("unable to allocate version");
+ return NULL;
+ }
+ strip(version);
+ return version;
+}
+
+
static const char *PKG_ERROR_STR[] = {
"success",
"[internal] unhandled package manager mode",
@@ -227,21 +252,30 @@ int pkg_index_provides(int mode, const char *index, const char *spec, const char
SYSERROR("Unable to create log directory: %s", logdir ? logdir : "NULL");
return -1;
}
- const char logfile_template[] = "STASIS-package_exists.XXXXXX";
- char logfile[PATH_MAX] = {0};
- snprintf(logfile, sizeof(logfile), "%s/%s", logdir, logfile_template);
- int logfd = mkstemp(logfile);
- if (logfd < 0) {
- SYSERROR("unable to create log file: %s", logfile);
- remove(logfile); // fail harmlessly if not present
- return PKG_INDEX_PROVIDES_E_INTERNAL_LOG_HANDLE;
+ const int stdout_stream = 0;
+ const int stderr_stream = 1;
+ const int stdout_st = 0;
+ //const int stderr_st = 1;
+ char logfile[2][PATH_MAX] = {0};
+ int logfile_fd[2] = {-1, -1};
+ struct stat logfile_st[2] = {0};
+
+ for (size_t i = 0; i < sizeof(logfile) / sizeof(logfile[0]); i++) {
+ const char logfile_template[] = "STASIS-package_exists.XXXXXX";
+ snprintf(logfile[i], sizeof(logfile[i]), "%s/%s", logdir, logfile_template);
+ logfile_fd[i] = mkstemp(logfile[i]);
+ if (logfile_fd[i] < 0) {
+ SYSERROR("unable to create log file: %s", logfile[i]);
+ remove(logfile[i]); // fail harmlessly if not present
+ return PKG_INDEX_PROVIDES_E_INTERNAL_LOG_HANDLE;
+ }
}
int status = 0;
struct Process proc = {0};
- proc.redirect_stderr = 1;
- snprintf(proc.f_stdout, sizeof(proc.f_stdout), "%s", logfile);
+ snprintf(proc.f_stdout, sizeof(proc.f_stdout), "%s", logfile[stdout_stream]);
+ snprintf(proc.f_stderr, sizeof(proc.f_stderr), "%s", logfile[stderr_stream]);
if (mode == PKG_USE_PIP) {
// Do an installation in dry-run mode to see if the package exists in the given index.
@@ -268,26 +302,40 @@ int pkg_index_provides(int mode, const char *index, const char *spec, const char
SYSDEBUG("Executing: %s", cmd);
status = shell(&proc, cmd);
- SYSDEBUG("Log file: %s", logfile);
- if (status != 0) {
- FILE *fp = fdopen(logfd, "r");
- if (!fp) {
- remove(logfile);
+ // Populate stat data for log files
+ for (size_t i = 0; i < sizeof(logfile) / sizeof(logfile[0]); i++) {
+ if (stat(logfile[i], &logfile_st[i])) {
+ SYSERROR("Unable to stat %s", logfile[i]);
return -1;
}
+ }
+
+ if (status != 0) {
+ SYSERROR("Command exited non-zero (%d)", status);
+ for (size_t i = 0; i < sizeof(logfile) / sizeof(logfile[0]); i++) {
+ const char *stream_name = i == 0 ? "stdout" : "stderr";
+ if (!logfile_st[i].st_size) {
+ continue;
+ }
+ SYSDEBUG("(%s): %s", stream_name, logfile[i]);
+ FILE *fp = fdopen(logfile_fd[i], "r");
+ if (!fp) {
+ remove(logfile[i]);
+ return -1;
+ }
- fflush(stdout);
- fflush(stderr);
+ fflush(stdout);
+ fflush(stderr);
- char line[STASIS_BUFSIZ] = {0};
- while (fgets(line, sizeof(line) - 1, fp) != NULL) {
- SYSDEBUG("%s", strip(line));
- }
+ char line[STASIS_BUFSIZ] = {0};
+ while (fgets(line, sizeof(line) - 1, fp) != NULL) {
+ SYSINFO("(%s): %s", stream_name, strip(line));
+ }
- fflush(stderr);
- fclose(fp);
+ fflush(stderr);
+ fclose(fp);
+ }
}
- remove(logfile);
if (WTERMSIG(proc.returncode)) {
// This gets its own return value because if the external program
@@ -295,18 +343,40 @@ int pkg_index_provides(int mode, const char *index, const char *spec, const char
return PKG_INDEX_PROVIDES_E_MANAGER_SIGNALED;
}
+ int final = PKG_FOUND;
if (status < 0) {
- return PKG_INDEX_PROVIDES_E_MANAGER_EXEC;
+ final = PKG_INDEX_PROVIDES_E_MANAGER_EXEC;
} else if (WEXITSTATUS(proc.returncode) > 1) {
// Pip and conda both return 2 on argument parsing errors
- return PKG_INDEX_PROVIDES_E_MANAGER_RUNTIME;
+ final = PKG_INDEX_PROVIDES_E_MANAGER_RUNTIME;
+ } else if (logfile_st[stdout_st].st_size > 1 && WEXITSTATUS(proc.returncode) == 0) {
+ // modern mamba return zero when a package not found.
+ // even more ridiculous; the error messages are written to stdout, not stderr
+ struct StrList *output = strlist_init();
+ if (!output) {
+ SYSERROR("unable to allocate memory for stdout log list");
+ return -1;
+ }
+ if (strlist_append_file(output, logfile[stdout_stream], NULL)) {
+ strlist_free(&output);
+ SYSERROR("unable to append stdout log to list: %s", logfile[stdout_stream]);
+ return -1;
+ }
+ if (strlist_contains(output, "No entries", NULL)) {
+ final = PKG_NOT_FOUND;
+ }
+ strlist_free(&output);
} else if (WEXITSTATUS(proc.returncode) == 1) {
// Pip and conda both return 1 when a package is not found.
// Unfortunately this applies to botched version specs, too.
- return PKG_NOT_FOUND;
- } else {
- return PKG_FOUND;
+ final = PKG_NOT_FOUND;
}
+
+ // Remove log files
+ for (size_t i = 0; i < sizeof(logfile) / sizeof(logfile[0]); i++) {
+ remove(logfile[stdout_stream]);
+ }
+ return final;
}
int conda_exec(const char *args) {
@@ -326,14 +396,22 @@ int conda_exec(const char *args) {
};
char conda_as[10] = {0};
+ const char *last_mamba_command = NULL;
safe_strncpy(conda_as, "conda", sizeof(conda_as));
for (size_t i = 0; mamba_commands[i] != NULL; i++) {
if (startswith(args, mamba_commands[i])) {
+ last_mamba_command = mamba_commands[i];
safe_strncpy(conda_as, "mamba", sizeof(conda_as));
break;
}
}
+ const int boa_present = find_program("boa") != NULL;
+ if (!boa_present) {
+ if (last_mamba_command && !strcmp(last_mamba_command, "build")) {
+ safe_strncpy(conda_as, "conda", sizeof(conda_as));
+ }
+ }
const char *command_fmt = "%s %s";
const int len = snprintf(NULL, 0, command_fmt, conda_as, args);
@@ -416,15 +494,12 @@ static int env0_to_runtime(const char *logfile) {
int conda_activate(const char *root, const char *env_name) {
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 = {0};
// Where to find conda's init scripts
snprintf(path_conda, sizeof(path_conda), "%s%s", root, init_script_conda);
- snprintf(path_mamba, sizeof(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
@@ -448,12 +523,6 @@ int conda_activate(const char *root, const char *env_name) {
return -1;
}
- if (access(path_mamba, F_OK) < 0) {
- SYSERROR("mamba is missing: %s, %s", path_mamba, strerror(errno));
- remove(logfile);
- return -1;
- }
-
// Fully activate conda and record its effect on the runtime environment
char command[PATH_MAX * 3];
const char *conda_shlvl_str = getenv("CONDA_SHLVL");
@@ -475,20 +544,19 @@ int conda_activate(const char *root, const char *env_name) {
snprintf(command, sizeof(command),
"set -a\n"
"source %s\n"
- "__conda_exe() (\n"
- " \"$CONDA_PYTHON_EXE\" \"$CONDA_EXE\" $_CE_M $_CE_CONDA \"$@\"\n"
- ")\n\n"
- "export -f __conda_exe\n"
- "source %s\n"
- "__mamba_exe() (\n"
- " \\local MAMBA_CONDA_EXE_BACKUP=$CONDA_EXE\n"
- " \\local MAMBA_EXE=$(\\dirname \"${CONDA_EXE}\")/mamba\n"
- " \"$CONDA_PYTHON_EXE\" \"$MAMBA_EXE\" $_CE_M $_CE_CONDA \"$@\"\n"
- ")\n\n"
- "export -f __mamba_exe\n"
+ "tempfile=$(mktemp)\n"
+ "chmod 600 \"${tempfile}\"\n"
+ "%s/bin/mamba shell init --shell bash --dry-run 2>/dev/null\\\n"
+ "| (ignore=1; \\\n"
+ " while read line; do \\\n"
+ " if [[ \"$line\" == \"#\"* ]]; then ignore=0; fi; \\\n"
+ " if (( ignore == 0 )); then echo $line; fi; \\\n"
+ " done) 1> \"${tempfile}\"\n"
+ "source \"${tempfile}\"\n"
+ "rm -f \"${tempfile}\"\n"
"%s\n"
"conda activate %s 1>&2\n"
- "env -0\n", path_conda, path_mamba, conda_shlvl ? "conda deactivate" : ":", env_name);
+ "env -0\n", path_conda, root, conda_shlvl ? "conda deactivate" : ":", env_name);
int retval = shell(&proc, command);
if (retval) {
@@ -561,7 +629,20 @@ int conda_check_required() {
return 0;
}
-int conda_setup_headless() {
+int conda_setup_headless(struct CondaCapabilities *cc) {
+ mkdirs(cc->prefix, 0755);
+
+ char rcpath[PATH_MAX];
+ snprintf(rcpath, sizeof(rcpath), "%s/.condarc", cc->prefix);
+ touch(rcpath);
+ if (errno == ENOENT) {
+ errno = 0;
+ }
+
+ setenv("CONDARC", rcpath, 1);
+ setenv("MAMBARC", rcpath, 1);
+ setenv("MAMBA_ROOT_PREFIX", cc->prefix, 1);
+
if (globals.verbose) {
conda_exec("config --system --set quiet false");
} else {
@@ -582,6 +663,18 @@ int conda_setup_headless() {
size_t total = 0;
const char *cmd_fmt = "'%s'";
if (globals.conda_packages && strlist_count(globals.conda_packages)) {
+ // Push or pop build packages based on capabilities
+ size_t boa_index = 0;
+ const int boa_requested = strlist_contains(globals.conda_packages, "boa", &boa_index);
+ if (boa_requested && !cc->require_boa) {
+ SYSWARN("Removing boa from global package list due to incompatible conda version (too new): %s", cc->conda_version);
+ strlist_remove(globals.conda_packages, boa_index);
+ } else if (!boa_requested && cc->require_boa) {
+ SYSWARN("Adding boa to global package list");
+ strlist_append(&globals.conda_packages, "boa");
+ }
+
+
memset(cmd, 0, sizeof(cmd));
safe_strncpy(cmd, "install ", sizeof(cmd));
@@ -719,7 +812,18 @@ int conda_env_remove(char *name) {
int conda_env_export(char *name, char *output_dir, char *output_filename) {
char env_command[PATH_MAX];
- snprintf(env_command, sizeof(env_command), "env export -n %s -f %s/%s.yml", name, output_dir, output_filename);
+ int vr = 0;
+ const char *env_format = NULL;
+ char *version = shell_output("conda --version", &vr);
+ if (version) {
+ const size_t v_offset = strlen("conda ");
+ if (version_compare(GT, version + v_offset, "25.1.0")) {
+ env_format = "--format=yml";
+ }
+ guard_free(version);
+ }
+
+ snprintf(env_command, sizeof(env_command), "env export %s -n %s -f %s/%s.yml", env_format ? env_format : "", name, output_dir, output_filename);
return conda_exec(env_command);
}
@@ -749,3 +853,59 @@ int conda_env_exists(const char *root, const char *name) {
snprintf(path, sizeof(path), "%s/envs/%s", root, name);
return access(path, F_OK) == 0;
}
+
+void conda_capable_free(struct CondaCapabilities *ccap) {
+ guard_free(ccap->conda_version);
+ guard_free(ccap->mamba_version);
+ memset(ccap, 0, sizeof(*ccap));
+}
+
+int conda_capable(struct CondaCapabilities *ccap, const char *root) {
+ struct CondaCapabilities *cc = ccap;
+ memset(cc, 0, sizeof(*cc));
+
+ if (find_program("conda")) {
+ cc->available = true;
+ }
+
+ if (cc->available) {
+ char *conda_version = python_importlib_metadata_version("conda");
+ if (!conda_version) {
+ SYSERROR("conda version detection failed");
+ return -1;
+ }
+
+ char *mamba_version = python_importlib_metadata_version("libmambapy");
+ if (!mamba_version) {
+ SYSERROR("unable to allocate mamba_version");
+ guard_free(conda_version);
+ return -1;
+ }
+
+ cc->prefix = root;
+ cc->conda_version = strdup(conda_version);
+ cc->mamba_version = strdup(mamba_version);
+ if (version_compare(GT | EQ, cc->conda_version, "25.3.0")) {
+ cc->require_explicit_export_format = true;
+ cc->require_boa = false;
+ } else {
+ cc->require_explicit_export_format = false;
+ cc->require_manual_activation_shim = true;
+ cc->require_boa = true;
+ cc->require_libmamba_solver = true;
+ }
+
+ struct Process proc = {0};
+ safe_strncpy(proc.f_stderr, "/dev/null", sizeof(proc.f_stderr));
+ safe_strncpy(proc.f_stdout, "/dev/null", sizeof(proc.f_stdout));
+ if (shell(&proc, "mamba install --use-local --dry-run conda")) {
+ cc->missing_use_local = true;
+ }
+
+ cc->usable = true;
+
+ guard_free(mamba_version);
+ guard_free(conda_version);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/src/lib/core/include/conda.h b/src/lib/core/include/conda.h
index a7108ec..9a6178b 100644
--- a/src/lib/core/include/conda.h
+++ b/src/lib/core/include/conda.h
@@ -26,6 +26,48 @@
#define PKG_INDEX_PROVIDES_E_MANAGER_EXEC (PKG_INDEX_PROVIDES_ERROR_MESSAGE_OFFSET + 5)
#define PKG_INDEX_PROVIDES_FAILED(ECODE) ((ECODE) <= PKG_INDEX_PROVIDES_ERROR_MESSAGE_OFFSET)
+/**
+ * @struct CondaCapabilities
+ * @brief Collection of feature flags to support older, newer, and transitional versions of conda/mamba.
+ * @see implementation in `conda_capable`
+ */
+struct CondaCapabilities {
+ /// Conda installation prefix
+ const char *prefix;
+ /// Currently installed version of Conda
+ char *conda_version;
+ /// Currently installed version of Mamba
+ char *mamba_version;
+ /// Is conda available in the runtime environment?
+ bool available;
+ /// Can conda execute?
+ bool usable;
+ /// Do we need to pass extra arguments to "conda env export"?
+ bool require_explicit_export_format;
+ /// Do we need to inject our own shim to make Conda available?
+ bool require_manual_activation_shim;
+ /// Does this version of Conda support building with boa?
+ bool require_boa;
+ /// Does this version of Conda need to be configured to use libmamba?
+ bool require_libmamba_solver;
+ /// Does "mamba install --use-local" work on this version of mamba?
+ bool missing_use_local;
+};
+
+/**
+ * Check for the existence of "Conda" and set flags based on the availability of features
+ * @param ccap a pointer to an empty `struct CondaCapabilities`
+ * @param root conda installation root directory
+ * @return 0 on success
+ */
+int conda_capable(struct CondaCapabilities *ccap, const char *root);
+
+/**
+ * Free memory allocated by `conda_capable`
+ * @param ccap a pointer to a populated `struct CondaCapabilities`
+ */
+void conda_capable_free(struct CondaCapabilities *ccap);
+
struct MicromambaInfo {
char *micromamba_prefix; //!< Path to write micromamba binary
char *conda_prefix; //!< Path to install conda base tree
@@ -83,6 +125,24 @@ int python_exec(const char *args);
int pip_exec(const char *args);
/**
+ * Use importlib to resolve the version of an installed package
+ *
+ * ```c
+ * char *numpy_version = python_importlib_metadata_version("numpy");
+ * if (!numpy_version) {
+ * fprintf(stderr, "failed to get numpy version\n");
+ * exit(1);
+ * }
+ *
+ * printf("numpy version: %s\n", numpy_version);
+ * free(numpy_version);
+ * ```
+ * @param package_name of installed python package
+ * @return
+ */
+char *python_importlib_metadata_version(const char *package_name);
+
+/**
* Execute conda (or if possible, mamba)
* Conda/Mamba is determined by PATH
*
@@ -117,7 +177,7 @@ int conda_activate(const char *root, const char *env_name);
/**
* Configure the active conda installation for headless operation
*/
-int conda_setup_headless();
+int conda_setup_headless(struct CondaCapabilities *cc);
/**
* Creates a Conda environment from a YAML config
diff --git a/src/lib/core/include/recipe.h b/src/lib/core/include/recipe.h
index 4dea248..ddcbabf 100644
--- a/src/lib/core/include/recipe.h
+++ b/src/lib/core/include/recipe.h
@@ -6,13 +6,20 @@
#include "utils.h"
//! Unable to determine recipe repo type
-#define RECIPE_TYPE_UNKNOWN 0
+#define RECIPE_STYLE_UNKNOWN 0
//! Recipe repo is from conda-forge
-#define RECIPE_TYPE_CONDA_FORGE 1
+#define RECIPE_STYLE_CONDA_FORGE 1
//! Recipe repo is from astroconda
-#define RECIPE_TYPE_ASTROCONDA 2
+#define RECIPE_STYLE_ASTROCONDA 2
//! Recipe repo provides the required build configurations but doesn't match conda-forge or astroconda's signature
-#define RECIPE_TYPE_GENERIC 3
+#define RECIPE_STYLE_GENERIC 3
+
+//! Unable to determine required build system
+#define RECIPE_BUILD_UNKNOWN 0
+//! Build uses meta.yaml
+#define RECIPE_BUILD_CONDA_BUILD 1
+//! Build uses recipe.yaml
+#define RECIPE_BUILD_RATTLER 2
/**
* Download a Conda package recipe
@@ -46,18 +53,18 @@ int recipe_clone(char *recipe_dir, char *url, char *gitref, char **result);
* }
*
* int recipe_type;
- * recipe_type = recipe_get_type(recipe);
+ * recipe_type = recipe_get_style(recipe);
* switch (recipe_type) {
- * case RECIPE_TYPE_CONDA_FORGE:
+ * case RECIPE_STYLE_CONDA_FORGE:
* // do something specific for conda-forge directory structure
* break;
- * case RECIPE_TYPE_ASTROCONDA:
+ * case RECIPE_STYLE_ASTROCONDA:
* // do something specific for astroconda directory structure
* break;
- * case RECIPE_TYPE_GENERIC:
+ * case RECIPE_STYLE_GENERIC:
* // do something specific for a directory containing a meta.yaml config
* break;
- * case RECIPE_TYPE_UNKNOWN:
+ * case RECIPE_STYLE_UNKNOWN:
* default:
* // the structure is foreign or the path doesn't contain a conda recipe
* break;
@@ -67,6 +74,7 @@ int recipe_clone(char *recipe_dir, char *url, char *gitref, char **result);
* @param repopath path to git repository containing conda recipe(s)
* @return One of RECIPE_TYPE_UNKNOWN, RECIPE_TYPE_CONDA_FORGE, RECIPE_TYPE_ASTROCONDA, RECIPE_TYPE_GENERIC
*/
-int recipe_get_type(char *repopath);
+int recipe_get_style(char *repopath);
+int recipe_get_build_system(const char *repopath, int style);
#endif //STASIS_RECIPE_H
diff --git a/src/lib/core/include/utils.h b/src/lib/core/include/utils.h
index 3f0fe9f..e75995a 100644
--- a/src/lib/core/include/utils.h
+++ b/src/lib/core/include/utils.h
@@ -429,7 +429,7 @@ int gen_file_extension_str(char *filename, size_t maxlen, const char *extension)
*/
char *remove_extras(char *s);
-void debug_hexdump(char *data, int len);
+void debug_hexdump(char *data, size_t len);
/**
* Realloc helper
diff --git a/src/lib/core/recipe.c b/src/lib/core/recipe.c
index 8cc8e21..2e18492 100644
--- a/src/lib/core/recipe.c
+++ b/src/lib/core/recipe.c
@@ -34,8 +34,26 @@ int recipe_clone(char *recipe_dir, char *url, char *gitref, char **result) {
return git_clone(&proc, url, destdir, gitref);
}
+int recipe_get_build_system(const char *repopath, const int style) {
+ char filename[PATH_MAX] = {0};
+ safe_strncat(filename, repopath, sizeof(filename));
-int recipe_get_type(char *repopath) {
+ if (style == RECIPE_STYLE_CONDA_FORGE) {
+ safe_strncat(filename, "/recipe/recipe.yaml", sizeof(filename));
+ if (!access(filename, F_OK)) {
+ return RECIPE_BUILD_RATTLER;
+ }
+ return RECIPE_BUILD_CONDA_BUILD;
+ }
+
+ if (style == RECIPE_STYLE_ASTROCONDA || style == RECIPE_STYLE_GENERIC) {
+ return RECIPE_BUILD_CONDA_BUILD;
+ }
+
+ return RECIPE_BUILD_UNKNOWN;
+}
+
+int recipe_get_style(char *repopath) {
// conda-forge is a collection of repositories
// "conda-forge.yml" is guaranteed to exist
const char *marker[] = {
@@ -44,10 +62,10 @@ int recipe_get_type(char *repopath) {
"meta.yaml",
NULL
};
- const int type[] = {
- RECIPE_TYPE_CONDA_FORGE,
- RECIPE_TYPE_ASTROCONDA,
- RECIPE_TYPE_GENERIC
+ const int style[] = {
+ RECIPE_STYLE_CONDA_FORGE,
+ RECIPE_STYLE_ASTROCONDA,
+ RECIPE_STYLE_GENERIC
};
for (size_t i = 0; marker[i] != NULL; i++) {
@@ -55,9 +73,9 @@ int recipe_get_type(char *repopath) {
snprintf(path, sizeof(path), "%s/%s", repopath, marker[i]);
int result = access(path, F_OK);
if (!result) {
- return type[i];
+ return style[i];
}
}
- return RECIPE_TYPE_UNKNOWN;
+ return RECIPE_STYLE_UNKNOWN;
} \ No newline at end of file
diff --git a/src/lib/core/system.c b/src/lib/core/system.c
index 4f1ece6..542f9fd 100644
--- a/src/lib/core/system.c
+++ b/src/lib/core/system.c
@@ -37,7 +37,9 @@ int shell(struct Process *proc, char *args) {
// Set the script's permissions so that only the calling user can use it
// This should help prevent eavesdropping if keys are applied in plain-text
// somewhere.
- chmod(t_name, 0700);
+ if (chmod(t_name, 0700)) {
+ SYSWARN("unable to change script permissions: %s, %s", t_name, strerror(errno));
+ }
pid_t pid = fork();
if (pid == -1) {
diff --git a/src/lib/core/utils.c b/src/lib/core/utils.c
index 31208ad..462604d 100644
--- a/src/lib/core/utils.c
+++ b/src/lib/core/utils.c
@@ -946,57 +946,43 @@ int gen_file_extension_str(char *filename, const size_t maxlen, const char *exte
return replace_text(ext_orig, ext_orig, extension, 0);
}
-#define DEBUG_HEXDUMP_FMT_BYTES 6
-#define DEBUG_HEXDUMP_ADDR_MAXLEN 20
-#define DEBUG_HEXDUMP_BYTES_MAXLEN (16 * 3 + 2)
-#define DEBUG_HEXDUMP_ASCII_MAXLEN (16 + 1)
-#define DEBUG_HEXDUMP_OUTPUT_MAXLEN (DEBUG_HEXDUMP_FMT_BYTES + DEBUG_HEXDUMP_ADDR_MAXLEN + DEBUG_HEXDUMP_BYTES_MAXLEN + DEBUG_HEXDUMP_ASCII_MAXLEN + 1)
-
-void debug_hexdump(char *data, int len) {
- int count = 0;
- char addr[DEBUG_HEXDUMP_ADDR_MAXLEN] = {0};
- char bytes[DEBUG_HEXDUMP_BYTES_MAXLEN] = {0};
- char ascii[DEBUG_HEXDUMP_ASCII_MAXLEN] = {0};
- char output[DEBUG_HEXDUMP_OUTPUT_MAXLEN] = {0};
+void debug_hexdump(char *data, const size_t len) {
+ if (!data) {
+ SYSWARN("DATA NULL\n");
+ return;
+ }
+ if (len <= 0) {
+ SYSWARN("ZERO LENGTH\n");
+ return;
+ }
+ const size_t window = 16;
+ size_t avail = len;
+ const size_t chunk = avail / window ? window : avail;
char *start = data;
- char *end = data + len;
-
- char *pos = start;
- while (pos != end) {
- if (count == 0) {
- snprintf(addr + strlen(addr), sizeof(addr) - strlen(addr), "%p", pos);
+ while (avail > 0) {
+ const size_t need = chunk > avail ? avail : chunk;
+ char *p = start;
+ printf("%p | ", p);
+ size_t j = 0;
+ for (j = 0; j < need; j++) {
+ printf("%02x ", p[j]);
}
- if (count == 8) {
- safe_strncat(bytes, " ", sizeof(bytes));
+ const size_t padding = window - j;
+ for (size_t i = 0; i < padding; i++) {
+ printf("00 ");
}
- if (count > 15) {
- snprintf(output, sizeof(output), "%s | %s | %s", addr, bytes, ascii);
- puts(output);
- memset(output, 0, sizeof(output));
- memset(addr, 0, sizeof(addr));
- memset(bytes, 0, sizeof(bytes));
- memset(ascii, 0, sizeof(ascii));
- count = 0;
- continue;
+ printf("| ");
+ for (j = 0; j < need; j++) {
+ char *c = p + j;
+ printf("%c", isprint(*c) ? *c : '.');
}
-
- snprintf(bytes + strlen(bytes), sizeof(bytes) - strlen(bytes), "%02X ", (unsigned char) *pos);
- snprintf(ascii + strlen(ascii), sizeof(ascii) - strlen(ascii), "%c", isprint(*pos) ? *pos : '.');
-
- pos++;
- count++;
- }
-
- if (count <= 8) {
- // Add group padding
- safe_strncat(bytes, " ", sizeof(bytes));
- }
- const int padding = 16 - count;
- for (int i = 0; i < padding; i++) {
- safe_strncat(bytes, " ", sizeof(bytes));
+ for (size_t i = 0; i < padding; i++) {
+ printf(".");
+ }
+ printf(" |\n");
+ avail -= need;
+ start += need;
}
- snprintf(output, sizeof(output), "%s | %s | %s", addr, bytes, ascii);
- puts(output);
}
int grow(const size_t size_new, size_t *size_orig, char **data) {
@@ -1295,7 +1281,7 @@ int is_file_compressed(const char *filename) {
{(unsigned char *) "PK\03\04", 3}, // zip
{(unsigned char *) "PK\05\06", 3}, // zip (empty)
{(unsigned char *) "PK\07\08", 3}, // zip (spanned)
- {(unsigned char *) "\xfd\x2f\xb5\x28", 4} // zstd
+ {(unsigned char *) "\x28\xb5\x2f\xfd", 4} // zstd
};
unsigned char buf[8] = {0}; // unsigned long
size_t bytes_read = 0;
diff --git a/src/lib/delivery/delivery.c b/src/lib/delivery/delivery.c
index dc9e2ce..d256681 100644
--- a/src/lib/delivery/delivery.c
+++ b/src/lib/delivery/delivery.c
@@ -236,6 +236,8 @@ void delivery_free(struct Delivery *ctx) {
guard_free(ctx->info.build_number);
guard_free(ctx->info.release_name);
guard_free(ctx->info.time_info);
+
+ conda_capable_free(&ctx->conda.capabilities);
guard_free(ctx->conda.installer_baseurl);
guard_free(ctx->conda.installer_name);
guard_free(ctx->conda.installer_version);
@@ -308,37 +310,38 @@ int delivery_format_str(struct Delivery *ctx, char **dest, size_t maxlen, const
i++;
switch (fmt[i]) {
case 'n': // name
- strncat(*dest, ctx->meta.name, maxlen - 1);
+ safe_strncat(*dest, ctx->meta.name, maxlen);
break;
case 'c': // codename
- strncat(*dest, ctx->meta.codename, maxlen - 1);
+ safe_strncat(*dest, ctx->meta.codename, maxlen);
break;
case 'm': // mission
- strncat(*dest, ctx->meta.mission, maxlen - 1);
+ safe_strncat(*dest, ctx->meta.mission, maxlen);
break;
case 'r': // revision
snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%d", ctx->meta.rc);
break;
case 'R': // "final"-aware revision
- if (ctx->meta.final)
- strncat(*dest, "final", maxlen);
- else
+ if (ctx->meta.final) {
+ safe_strncat(*dest, "final", maxlen);
+ } else {
snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%d", ctx->meta.rc);
+ }
break;
case 'v': // version
- strncat(*dest, ctx->meta.version, maxlen - 1);
+ safe_strncat(*dest, ctx->meta.version, maxlen);
break;
case 'P': // python version
- strncat(*dest, ctx->meta.python, maxlen - 1);
+ safe_strncat(*dest, ctx->meta.python, maxlen);
break;
case 'p': // python version major/minor
- strncat(*dest, ctx->meta.python_compact, maxlen - 1);
+ safe_strncat(*dest, ctx->meta.python_compact, maxlen);
break;
case 'a': // system architecture name
- strncat(*dest, ctx->system.arch, maxlen - 1);
+ safe_strncat(*dest, ctx->system.arch, maxlen);
break;
case 'o': // system platform (OS) name
- strncat(*dest, ctx->system.platform[DELIVERY_PLATFORM_RELEASE], maxlen - 1);
+ safe_strncat(*dest, ctx->system.platform[DELIVERY_PLATFORM_RELEASE], maxlen);
break;
case 't': // unix epoch
snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%ld", ctx->info.time_now);
@@ -363,11 +366,11 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
if (DEFER_CONDA == type) {
dataptr = ctx->conda.conda_packages;
deferred = ctx->conda.conda_packages_defer;
- strncpy(mode, "conda", sizeof(mode) - 1);
+ safe_strncpy(mode, "conda", sizeof(mode));
} else if (DEFER_PIP == type) {
dataptr = ctx->conda.pip_packages;
deferred = ctx->conda.pip_packages_defer;
- strncpy(mode, "pip", sizeof(mode) - 1);
+ safe_strncpy(mode, "pip", sizeof(mode));
} else {
SYSERROR("BUG: type %d does not map to a supported package manager!", type);
exit(1);
@@ -397,11 +400,10 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
while (*spec_end != '\0' && !isalnum(*spec_end)) {
spec_end++;
}
- strncpy(package_name, name, spec_begin - name);
- package_name[spec_begin - name] = '\0';
+ size_t spec_len = spec_begin - name;
+ safe_strncpy(package_name, name, spec_len ? spec_len + 1 : sizeof(package_name));
} else {
- strncpy(package_name, name, sizeof(package_name) - 1);
- package_name[sizeof(package_name) - 1] = '\0';
+ safe_strncpy(package_name, name, sizeof(package_name));
}
remove_extras(package_name);
@@ -412,8 +414,7 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
struct Test *test = ctx->tests->test[x];
char nametmp[STASIS_NAME_MAX] = {0};
- strncpy(nametmp, package_name, sizeof(nametmp) - 1);
- nametmp[sizeof(nametmp) - 1] = '\0';
+ safe_strncpy(nametmp, package_name, sizeof(nametmp));
// Is the [test:NAME] in the package name?
if (!strcmp(nametmp, test->name)) {
@@ -479,6 +480,10 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
}
}
+ if (getenv("STASIS_ALWAYS_BUILD_FOR_HOST")) {
+ build_for_host = 1;
+ }
+
if (build_for_host) {
printf("BUILD FOR HOST\n");
strlist_append(&deferred, name);
diff --git a/src/lib/delivery/delivery_build.c b/src/lib/delivery/delivery_build.c
index d8674e0..9ef5d92 100644
--- a/src/lib/delivery/delivery_build.c
+++ b/src/lib/delivery/delivery_build.c
@@ -16,11 +16,13 @@ int delivery_build_recipes(struct Delivery *ctx) {
SYSERROR("BUG: recipe_clone() succeeded but recipe_dir is NULL: %s", strerror(errno));
return -1;
}
- int recipe_type = recipe_get_type(recipe_dir);
+ const int recipe_style = recipe_get_style(recipe_dir);
+ const int recipe_build_system = recipe_get_build_system(recipe_dir, recipe_style);
+
if(!pushd(recipe_dir)) {
- if (RECIPE_TYPE_ASTROCONDA == recipe_type) {
+ if (RECIPE_STYLE_ASTROCONDA == recipe_style) {
pushd(path_basename(ctx->tests->test[i]->repository));
- } else if (RECIPE_TYPE_CONDA_FORGE == recipe_type) {
+ } else if (RECIPE_STYLE_CONDA_FORGE == recipe_style) {
pushd("recipe");
}
@@ -56,23 +58,28 @@ int delivery_build_recipes(struct Delivery *ctx) {
snprintf(recipe_buildno, sizeof(recipe_buildno), " number: 0");
unsigned flags = REPLACE_TRUNCATE_AFTER_MATCH;
- //file_replace_text("meta.yaml", "{% set version = ", recipe_version);
- if (ctx->meta.final) { // remove this. i.e. statis cannot deploy a release to conda-forge
- snprintf(recipe_version, sizeof(recipe_version), "{%% set version = \"%s\" %%}", ctx->tests->test[i]->version);
- // TODO: replace sha256 of tagged archive
- // TODO: leave the recipe unchanged otherwise. in theory this should produce the same conda package hash as conda forge.
- // For now, remove the sha256 requirement
- file_replace_text("meta.yaml", "sha256:", "\n", flags);
- } else {
- file_replace_text("meta.yaml", "{% set version = ", recipe_version, flags);
- file_replace_text("meta.yaml", " url:", recipe_git_url, flags);
- //file_replace_text("meta.yaml", "sha256:", recipe_git_rev);
- file_replace_text("meta.yaml", " sha256:", "\n", flags);
- file_replace_text("meta.yaml", " number:", recipe_buildno, flags);
+ if (recipe_build_system == RECIPE_BUILD_CONDA_BUILD) {
+ if (ctx->meta.final) { // remove this. i.e. statis cannot deploy a release to conda-forge
+ snprintf(recipe_version, sizeof(recipe_version), "{%% set version = \"%s\" %%}", ctx->tests->test[i]->version);
+ // TODO: replace sha256 of tagged archive
+ // TODO: leave the recipe unchanged otherwise. in theory this should produce the same conda package hash as conda forge.
+ // For now, remove the sha256 requirement
+ file_replace_text("meta.yaml", "sha256:", "\n", flags);
+ } else {
+ file_replace_text("meta.yaml", "{% set version = ", recipe_version, flags);
+ file_replace_text("meta.yaml", " url:", recipe_git_url, flags);
+ file_replace_text("meta.yaml", " sha256:", "\n", flags);
+ file_replace_text("meta.yaml", " number:", recipe_buildno, flags);
+ }
+ } else if (recipe_build_system == RECIPE_BUILD_RATTLER) {
+ file_replace_text("recipe.yaml", " version:", ctx->tests->test[i]->version, flags);
+ file_replace_text("recipe.yaml", " url:", recipe_git_url, flags);
+ file_replace_text("recipe.yaml", " sha256:", "\n", flags);
+ file_replace_text("recipe.yaml", " number:", recipe_buildno, flags);
}
char command[PATH_MAX];
- if (RECIPE_TYPE_CONDA_FORGE == recipe_type) {
+ if (RECIPE_STYLE_CONDA_FORGE == recipe_style) {
char arch[STASIS_NAME_MAX] = {0};
char platform[STASIS_NAME_MAX] = {0};
@@ -92,18 +99,34 @@ int delivery_build_recipes(struct Delivery *ctx) {
}
tolower_s(arch);
- snprintf(command, sizeof(command), "mambabuild --python=%s -m ../.ci_support/%s_%s_.yaml .",
- ctx->meta.python, platform, arch);
+ // default build tool is "conda build" aka "build"
+ char tool[STASIS_NAME_MAX] = "build";
+ if (recipe_build_system == RECIPE_BUILD_CONDA_BUILD) {
+ if (strlist_contains(globals.conda_packages, "boa", NULL)) {
+ safe_strncpy(tool, "mambabuild", sizeof(tool));
+ }
+ } else if (recipe_build_system == RECIPE_BUILD_RATTLER) {
+ snprintf(tool, sizeof(tool), "rattler-build");
+ }
+ snprintf(command, sizeof(command), "%s --python=%s -m ../.ci_support/%s_%s_.yaml .",
+ tool, ctx->meta.python, platform, arch);
+ } else {
+ snprintf(command, sizeof(command), "build --python=%s .", ctx->meta.python);
+ }
+
+ int status = 0;
+ if (recipe_build_system == RECIPE_BUILD_RATTLER) {
+ // rattler-build is a standalone program, not a conda sub-command
+ status = system(command);
} else {
- snprintf(command, sizeof(command), "mambabuild --python=%s .", ctx->meta.python);
+ status = conda_exec(command);
}
- int status = conda_exec(command);
if (status) {
guard_free(recipe_dir);
return -1;
}
- if (RECIPE_TYPE_GENERIC != recipe_type) {
+ if (RECIPE_STYLE_GENERIC != recipe_style) {
popd();
}
popd();
diff --git a/src/lib/delivery/delivery_conda.c b/src/lib/delivery/delivery_conda.c
index 117e6c9..4f2920e 100644
--- a/src/lib/delivery/delivery_conda.c
+++ b/src/lib/delivery/delivery_conda.c
@@ -103,24 +103,61 @@ void delivery_install_conda(char *install_script, char *conda_install_dir) {
}
}
-void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir) {
- if (conda_activate(conda_install_dir, "base")) {
- SYSERROR("conda activation failed");
- exit(1);
- }
+void delivery_conda_enable(struct Delivery *ctx) {
+ setenv("MAMBA_ROOT_PREFIX", ctx->storage.conda_install_prefix, 1);
// Setting the CONDARC environment variable appears to be the only consistent
// way to make sure the file is used. Not setting this variable leads to strange
// behavior, especially if a conda environment is already active when STASIS is loaded.
char rcpath[PATH_MAX];
- snprintf(rcpath, sizeof(rcpath), "%s/%s", conda_install_dir, ".condarc");
+ snprintf(rcpath, sizeof(rcpath), "%s/%s", ctx->storage.conda_install_prefix, ".condarc");
setenv("CONDARC", rcpath, 1);
+ setenv("MAMBARC", rcpath, 1);
if (runtime_replace(&ctx->runtime.environ, __environ)) {
SYSERROR("unable to replace runtime environment after activating conda");
exit(1);
}
- if (conda_setup_headless()) {
+ if (conda_activate(ctx->storage.conda_install_prefix, "base")) {
+ SYSERROR("conda activation failed");
+ exit(1);
+ }
+
+ char pinned[PATH_MAX];
+ snprintf(pinned, sizeof(pinned), "%s/conda-meta/pinned", ctx->storage.conda_install_prefix);
+ touch(pinned);
+ if (errno == ENOENT) {
+ errno = 0;
+ }
+
+ char *conda_version = strdup(ctx->conda.installer_version);
+ if (conda_version) {
+ char *rev = strpbrk(conda_version, "-");
+ if (rev) {
+ *rev = '\0';
+ }
+
+ FILE *pinned_fp = fopen(pinned, "w+");
+ if (!pinned_fp) {
+ SYSERROR("unable to open conda-meta/pinned file for writing: %s", strerror(errno));
+ exit(1);
+ }
+ fprintf(pinned_fp, "conda=%s\n", conda_version);
+ fclose(pinned_fp);
+ guard_free(conda_version);
+ }
+
+ if (conda_capable(&ctx->conda.capabilities, ctx->storage.conda_install_prefix)) {
+ SYSERROR("Conda capability check failed");
+ exit(1);
+ }
+
+ if (!ctx->conda.capabilities.usable) {
+ SYSERROR("Conda is broken");
+ exit(1);
+ }
+
+ if (conda_setup_headless(&ctx->conda.capabilities)) {
// no COE check. this call must succeed.
exit(1);
}
diff --git a/src/lib/delivery/delivery_populate.c b/src/lib/delivery/delivery_populate.c
index 5ce11d7..314ef46 100644
--- a/src/lib/delivery/delivery_populate.c
+++ b/src/lib/delivery/delivery_populate.c
@@ -199,6 +199,44 @@ static void normalize_ini_list(struct INIFILE **inip, struct StrList **listp, ch
(*listp) = list;
}
+static int check_package_spec_list(struct StrList *list, const char *ini_section, const char *ini_key) {
+ if (!list) {
+ // empty lists are OK
+ return 0;
+ }
+ for (size_t i = 0; i < strlist_count(list); i++) {
+ const char *item = strlist_item(list, i);
+ if (!item) {
+ continue;
+ }
+ const char *invalid_chars = "@:/<>!~";
+ const char *invalid_spec = strpbrk(item, invalid_chars);
+ if (invalid_spec) {
+ SYSERROR("Invalid version specification detected at %s:%s[%zu], \"%s\"", ini_section, ini_key, i, item);
+ SYSERROR("Package specification may not contain the operator '%c' (or any of \"%s\")", *invalid_spec, invalid_chars);
+ SYSERROR("");
+ SYSERROR("%s:%s supports:", ini_section, ini_key);
+ SYSERROR(" {package}[=={version|tag|branch|ref}]");
+ SYSERROR("");
+ char *reported_name = strdup(item);
+ if (!reported_name) {
+ SYSERROR("unable to allocate memory for reported_name");
+ return -1;
+ }
+ const char *reported_name_end = reported_name;
+ while (!ispunct(*reported_name_end)) {
+ reported_name_end++;
+ }
+ reported_name[reported_name_end - reported_name] = '\0';
+ SYSERROR("Set test:%s.repository to point to a valid Git repository URL to enable [tag|branch|ref]", reported_name);
+
+ guard_free(reported_name);
+ return -1;
+ }
+ }
+ return 0;
+}
+
int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
struct INIFILE *ini = ctx->_stasis_ini_fp.delivery;
struct INIData *rtdata;
@@ -235,12 +273,22 @@ int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
ctx->conda.installer_platform = ini_getval_str(ini, "conda", "installer_platform", render_mode, &err);
ctx->conda.installer_arch = ini_getval_str(ini, "conda", "installer_arch", render_mode, &err);
ctx->conda.installer_baseurl = ini_getval_str(ini, "conda", "installer_baseurl", render_mode, &err);
+
ctx->conda.conda_packages = ini_getval_strlist(ini, "conda", "conda_packages", LINE_SEP, render_mode, &err);
normalize_ini_list(&ini, &ctx->conda.conda_packages, "conda", "conda_packages", render_mode);
+ if (check_package_spec_list(ctx->conda.conda_packages, "conda", "conda_packages")) {
+ return -1;
+ }
+
ctx->conda.conda_packages_purge = ini_getval_strlist(ini, "conda", "conda_packages_purge", LINE_SEP, render_mode, &err);
normalize_ini_list(&ini, &ctx->conda.conda_packages_purge, "conda", "conda_package_purge", render_mode);
+
ctx->conda.pip_packages = ini_getval_strlist(ini, "conda", "pip_packages", LINE_SEP, render_mode, &err);
normalize_ini_list(&ini, &ctx->conda.pip_packages, "conda", "pip_packages", render_mode);
+ if (check_package_spec_list(ctx->conda.pip_packages, "conda", "pip_packages")) {
+ return -1;
+ }
+
ctx->conda.pip_packages_purge = ini_getval_strlist(ini, "conda", "pip_packages_purge", LINE_SEP, render_mode, &err);
normalize_ini_list(&ini, &ctx->conda.pip_packages_purge, "conda", "pip_packages_purge", render_mode);
diff --git a/src/lib/delivery/include/delivery.h b/src/lib/delivery/include/delivery.h
index 3103a86..7d846ab 100644
--- a/src/lib/delivery/include/delivery.h
+++ b/src/lib/delivery/include/delivery.h
@@ -10,6 +10,7 @@
#include "environment.h"
#include "ini.h"
#include "multiprocessing.h"
+#include "conda.h"
#define DELIVERY_PLATFORM_MAX 4
#define DELIVERY_PLATFORM_MAXLEN 65
@@ -157,6 +158,7 @@ struct Delivery {
struct StrList *pip_packages_defer; ///< Python packages to be built for delivery
struct StrList *pip_packages_purge; ///< Python packages to remove from a delivery (for: based_on)
struct StrList *wheels_packages; ///< Wheel packages built for delivery
+ struct CondaCapabilities capabilities; ///< Capability information
} conda;
/*! \struct Runtime
@@ -354,9 +356,8 @@ void delivery_defer_packages(struct Delivery *ctx, int type);
/**
* Configure and activate a Conda installation based on Delivery context
* @param ctx pointer to Delivery context
- * @param conda_install_dir path to Conda installation
*/
-void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir);
+void delivery_conda_enable(struct Delivery *ctx);
/**
* Install Conda
diff --git a/stasis.ini b/stasis.ini
index fdecc86..79f3e36 100644
--- a/stasis.ini
+++ b/stasis.ini
@@ -16,9 +16,9 @@ conda_fresh_start = true
; (list) Conda packages to be installed/overridden in the base environment
conda_packages =
- conda-build>=3.22.0
boa
- conda-libmamba-solver
+ conda-build
+ rattler-build
; (list) Python packages to be installed/overridden in the base environment
;pip_packages =
diff --git a/tests/data/compression/bz2 b/tests/data/compression/bz2
new file mode 100644
index 0000000..20d3517
--- /dev/null
+++ b/tests/data/compression/bz2
Binary files differ
diff --git a/tests/data/compression/gz b/tests/data/compression/gz
new file mode 100644
index 0000000..2762cd9
--- /dev/null
+++ b/tests/data/compression/gz
Binary files differ
diff --git a/tests/data/compression/none b/tests/data/compression/none
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/tests/data/compression/none
@@ -0,0 +1 @@
+hello
diff --git a/tests/data/compression/xz b/tests/data/compression/xz
new file mode 100644
index 0000000..f54e3fc
--- /dev/null
+++ b/tests/data/compression/xz
Binary files differ
diff --git a/tests/data/compression/zip b/tests/data/compression/zip
new file mode 100644
index 0000000..1f77925
--- /dev/null
+++ b/tests/data/compression/zip
Binary files differ
diff --git a/tests/data/compression/zstd b/tests/data/compression/zstd
new file mode 100644
index 0000000..a827868
--- /dev/null
+++ b/tests/data/compression/zstd
Binary files differ
diff --git a/tests/data/gbo_ng.ini b/tests/data/gbo_ng.ini
new file mode 100644
index 0000000..355e5ee
--- /dev/null
+++ b/tests/data/gbo_ng.ini
@@ -0,0 +1,66 @@
+[meta]
+mission = generic
+name = GBO
+version = 2.4.6
+rc = 1
+final = false
+based_on = {{ env:TEST_DATA }}/gbo.yml
+python = 3.11
+
+
+[conda]
+installer_name = Miniforge3
+installer_version = 26.3.2-2
+installer_platform = {{env:STASIS_CONDA_PLATFORM}}
+installer_arch = {{env:STASIS_CONDA_ARCH}}
+installer_baseurl = https://github.com/conda-forge/miniforge/releases/download/{{conda.installer_version}}
+;conda_packages =
+pip_packages =
+ firewatch==0.0.4
+ gwcs==0.22.1
+ tweakwcs==0.9.0
+
+
+[runtime]
+CPPFLAGS = ${CPPFLAGS} -fpermissive
+PYTHONUNBUFFERED = 1
+
+
+[test:firewatch]
+repository = https://github.com/astroconda/firewatch
+script_setup =
+ pip install -e '.'
+script =
+ firewatch -c conda-forge -p ${STASIS_CONDA_PLATFORM_SUBDIR} | grep -E ' python-[0-9]'
+
+
+[test:tweakwcs]
+repository = https://github.com/spacetelescope/tweakwcs
+script_setup =
+ pip install -e '.[test]'
+script =
+ pytest \
+ -r fEsx \
+ --basetemp="{{ func:basetemp_dir() }}" \
+ --junitxml="{{ func:junitxml_file() }}"
+
+
+[deploy:artifactory:delivery]
+files =
+ {{ storage.output_dir }}/**
+dest = {{ meta.mission }}/{{ info.build_name }}/
+
+
+[deploy:docker]
+registry = bytesalad.stsci.edu
+image_compression = zstd -v -9 -c
+build_args =
+ SNAPSHOT_INPUT={{ info.release_name }}.yml
+ SNAPSHOT_PKGDIR=packages
+tags =
+ {{ meta.name }}:{{ info.build_number }}-py{{ meta.python_compact }}
+ {{ deploy.docker.registry }}/{{ meta.name }}:{{ info.build_number }}-py{{ meta.python_compact }}
+test_script =
+ source /etc/profile
+ python -m pip freeze
+ mamba info
diff --git a/tests/include/testing.h b/tests/include/testing.h
index d11398c..a669b5d 100644
--- a/tests/include/testing.h
+++ b/tests/include/testing.h
@@ -11,7 +11,7 @@
#ifdef STASIS_TEST_VERBOSE
#define STASIS_TEST_MSG(MSG, ...) do { \
-fprintf(stderr, "%s:%d:%s(): ", path_basename(__FILE__), __LINE__, __FUNCTION__); \
+fprintf(stderr, "%s:%d:%s(): ", path_basename(__FILE__), __LINE__, __func__); \
fprintf(stderr, MSG LINE_SEP, __VA_ARGS__); \
} while (0)
#else
@@ -76,6 +76,7 @@ inline void stasis_testing_record_result_summary() {
do_message = 1;
#endif
strcpy(status_msg, "PASS");
+ do_reason = 0;
passed++;
}
if (do_message) {
diff --git a/tests/rt_generic_ng_based_on.sh b/tests/rt_generic_ng_based_on.sh
new file mode 100644
index 0000000..de5af24
--- /dev/null
+++ b/tests/rt_generic_ng_based_on.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+here="$(dirname ${BASH_SOURCE[0]})"
+source $here/setup.sh
+
+TEST_NAME=gbo_ng
+PYTHON_VERSIONS=(
+ 3.11
+)
+setup_workspace "$TEST_NAME"
+run_command install_stasis
+
+ln -s "$TEST_DATA"/"$TEST_NAME".yml
+for py_version in "${PYTHON_VERSIONS[@]}"; do
+ run_command run_stasis --python "$py_version" \
+ --no-docker \
+ --no-artifactory \
+ "$TEST_DATA"/"$TEST_NAME".ini
+done
+
+check_output_add "(null)"
+run_command check_output_stasis_dir stasis/*/output
+check_output_reset
+
+# NOTE: indexer default output directory is "output"
+check_output_add "(null)"
+run_command run_stasis_indexer stasis
+run_command check_output_indexed_dir output
+check_output_reset
+
+teardown_workspace "$TEST_NAME"
diff --git a/tests/test_conda.c b/tests/test_conda.c
index 4d0b4d8..7de1275 100644
--- a/tests/test_conda.c
+++ b/tests/test_conda.c
@@ -2,15 +2,14 @@
#include "conda.h"
#include "delivery.h"
-char cwd_start[PATH_MAX];
-char cwd_workspace[PATH_MAX];
int conda_is_installed = 0;
void test_micromamba() {
+ const char *cwd_workspace = TEST_WORKSPACE_DIR;
char mm_prefix[PATH_MAX] = {0};
char c_prefix[PATH_MAX] = {0};
- snprintf(mm_prefix, strlen(cwd_workspace) + strlen("micromamba") + 2, "%s/%s", cwd_workspace, "micromamba");
- snprintf(c_prefix, strlen(mm_prefix) + strlen("conda") + 2, "%s/%s", mm_prefix, "conda");
+ snprintf(mm_prefix, sizeof(mm_prefix), "%s/%s/%s", cwd_workspace, "tools", "micromamba");
+ snprintf(c_prefix, sizeof(c_prefix), "%s/%s", mm_prefix, "conda");
struct testcase {
struct MicromambaInfo mminfo;
@@ -18,10 +17,10 @@ void test_micromamba() {
int result;
};
struct testcase tc[] = {
- {.mminfo = {.download_dir = cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "info", .result = 0},
- {.mminfo = {.download_dir = cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "env list", .result = 0},
- {.mminfo = {.download_dir = cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "run python3 -V", .result = 0},
- {.mminfo = {.download_dir = cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "no_such_option", .result = 109},
+ {.mminfo = {.download_dir = (char *) cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "info", .result = 0},
+ {.mminfo = {.download_dir = (char *) cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "env list", .result = 0},
+ {.mminfo = {.download_dir = (char *) cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "run python3 -V", .result = 0},
+ {.mminfo = {.download_dir = (char *) cwd_workspace, .micromamba_prefix = mm_prefix, .conda_prefix = c_prefix}, .cmd = "no_such_option", .result = 109},
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
@@ -67,7 +66,7 @@ void test_conda_activate() {
STASIS_SKIP_IF(!conda_is_installed, "cannot run without conda");
STASIS_ASSERT_FATAL(conda_activate(ctx.storage.conda_install_prefix, "base") == 0, "unable to activate base environment");
-
+ runtime_replace(&ctx.runtime.environ, __environ);
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
struct testcase *item = &tc[i];
char *value = getenv(item->key);
@@ -102,11 +101,13 @@ void test_python_exec() {
void test_conda_setup_headless() {
globals.conda_packages = strlist_init();
globals.pip_packages = strlist_init();
- strlist_append(&globals.conda_packages, "boa");
strlist_append(&globals.conda_packages, "conda-build");
- strlist_append(&globals.conda_packages, "conda-verify");
strlist_append(&globals.pip_packages, "pytest");
- STASIS_ASSERT(conda_setup_headless() == 0, "headless configuration failed");
+ struct CondaCapabilities cc;
+ int capable = conda_capable(&cc, ctx.storage.conda_install_prefix);
+ STASIS_ASSERT_FATAL(capable == EXIT_SUCCESS, "conda capability check function should not fail");
+ STASIS_ASSERT(conda_setup_headless(&cc) == 0, "headless configuration failed");
+ conda_capable_free(&cc);
}
void test_conda_env_create_from_uri() {
@@ -207,18 +208,10 @@ int main(int argc, char *argv[]) {
test_delivery_gather_tool_versions,
};
- char ws[] = "workspace_XXXXXX";
- if (!mkdtemp(ws)) {
- SYSERROR("unable to mkdtemp: %s", strerror(errno));
- exit(1);
- }
- getcwd(cwd_start, sizeof(cwd_start) - 1);
- mkdir(ws, 0755);
- chdir(ws);
- getcwd(cwd_workspace, sizeof(cwd_workspace) - 1);
- snprintf(conda_prefix, strlen(cwd_workspace) + strlen("conda") + 2, "%s/conda", cwd_workspace);
+ snprintf(conda_prefix, strlen(TEST_WORKSPACE_DIR) + strlen("conda") + 2, "%s/conda", TEST_WORKSPACE_DIR);
+ const char *installer_version = "26.3.2-2";
const char *mockinidata = "[meta]\n"
"name = mock\n"
"version = 1.0.0\n"
@@ -227,18 +220,21 @@ int main(int argc, char *argv[]) {
"python = 3.11\n"
"[conda]\n"
"installer_name = Miniforge3\n"
- "installer_version = 24.3.0-0\n"
+ "installer_version = %s\n"
"installer_platform = {{env:STASIS_CONDA_PLATFORM}}\n"
"installer_arch = {{env:STASIS_CONDA_ARCH}}\n"
- "installer_baseurl = https://github.com/conda-forge/miniforge/releases/download/24.3.0-0\n";
- stasis_testing_write_ascii("mock.ini", mockinidata);
+ "installer_baseurl = https://github.com/conda-forge/miniforge/releases/download/%s\n";
+ char mockinidata_final[STASIS_BUFSIZ] = {0};
+ snprintf(mockinidata_final, sizeof(mockinidata_final), mockinidata, installer_version, installer_version);
+
+ stasis_testing_write_ascii("mock.ini", mockinidata_final);
struct INIFILE *ini = ini_open("mock.ini");
ctx._stasis_ini_fp.delivery = ini;
ctx._stasis_ini_fp.delivery_path = realpath("mock.ini", NULL);
const char *sysconfdir = getenv("STASIS_SYSCONFDIR");
globals.sysconfdir = strdup(sysconfdir ? sysconfdir : STASIS_SYSCONFDIR);
- ctx.storage.root = strdup(cwd_workspace);
+ ctx.storage.root = strdup(TEST_WORKSPACE_DIR);
setenv("LANG", "C", 1);
bootstrap_build_info(&ctx);
@@ -246,10 +242,6 @@ int main(int argc, char *argv[]) {
STASIS_TEST_RUN(tests);
- chdir(cwd_start);
- if (rmtree(cwd_workspace)) {
- perror(cwd_workspace);
- }
delivery_free(&ctx);
globals_free();
STASIS_TEST_END_MAIN();
diff --git a/tests/test_recipe.c b/tests/test_recipe.c
index 3ea21ce..939ac7d 100644
--- a/tests/test_recipe.c
+++ b/tests/test_recipe.c
@@ -33,27 +33,27 @@ void test_recipe_clone() {
{.recipe_dir = "recipe_condaforge",
.url = "https://github.com/conda-forge/fitsverify-feedstock",
.gitref = "HEAD",
- .expect_type = RECIPE_TYPE_CONDA_FORGE,
+ .expect_type = RECIPE_STYLE_CONDA_FORGE,
.expect_return = 0},
{.recipe_dir = "recipe_astroconda",
.url = "https://github.com/astroconda/astroconda-contrib",
.gitref = "HEAD",
- .expect_type = RECIPE_TYPE_ASTROCONDA,
+ .expect_type = RECIPE_STYLE_ASTROCONDA,
.expect_return = 0},
{.recipe_dir = "recipe_generic",
.url = "local_repo",
.gitref = "HEAD",
- .expect_type = RECIPE_TYPE_GENERIC,
+ .expect_type = RECIPE_STYLE_GENERIC,
.expect_return = 0},
{.recipe_dir = "recipe_unknown",
.url = "https://github.com/astroconda/firewatch",
.gitref = "HEAD",
- .expect_type = RECIPE_TYPE_UNKNOWN,
+ .expect_type = RECIPE_STYLE_UNKNOWN,
.expect_return = 0},
{.recipe_dir = "recipe_broken",
.url = "123_BAD_BAD_BAD_456",
.gitref = "HEAD",
- .expect_type = RECIPE_TYPE_UNKNOWN,
+ .expect_type = RECIPE_STYLE_UNKNOWN,
.expect_return = 128},
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
@@ -76,7 +76,7 @@ void test_recipe_clone() {
// Ensure a path to the repository was returned in the result argument
STASIS_ASSERT(result_path != NULL, "result path should not be NULL");
// Verify the repository was detected as the correct recipe type
- STASIS_ASSERT(recipe_get_type(result_path) == test->expect_type, "repository detected as the wrong type");
+ STASIS_ASSERT(recipe_get_style(result_path) == test->expect_type, "repository detected as the wrong type");
if (test->expect_return == 0) {
// Verify the result path exists
diff --git a/tests/test_str.c b/tests/test_str.c
index 09d8809..26dcf4c 100644
--- a/tests/test_str.c
+++ b/tests/test_str.c
@@ -16,7 +16,7 @@ void test_to_short_version() {
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
char *result = to_short_version(tc[i].data);
STASIS_ASSERT_FATAL(result != NULL, "should not be NULL");
- //printf("%s[%zu], result: %s, expected: %s\n", __FUNCTION__, i, result, tc[i].expected);
+ printf("%s[%zu], result: %s, expected: %s\n", __func__, i, result, tc[i].expected);
STASIS_ASSERT(strcmp(result, tc[i].expected) == 0, "unexpected result");
guard_free(result);
}
@@ -110,12 +110,17 @@ void test_strchrdel() {
const struct testcase tc[] = {
{.data ="aaaabbbbcccc", .input = "ac", .expected = "bbbb"},
{.data = "1I 2have 3a 4pencil 5box.", .input = "1245", .expected = "I have 3a pencil box."},
+ {.data = "1I 2have 3a 4pencil 5box.", .input = "12345", .expected = "I have a pencil box."},
{.data = "\v\v\vI\t\f ha\tve a\t pen\tcil b\tox.", .input = " \f\t\v", "Ihaveapencilbox."},
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
char *data = strdup(tc[i].data);
+ printf("data before: %s\n", data);
+ debug_hexdump(data, strlen(data) + 1);
strchrdel(data, tc[i].input);
+ printf("data after: %s\n", data);
+ debug_hexdump(data, strlen(data) + 1);
STASIS_ASSERT(strcmp(data, tc[i].expected) == 0, "wrong status for condition");
guard_free(data);
}
@@ -184,7 +189,10 @@ void test_num_chars() {
{.data = "abc\t\ndef\nabc\ndef\n", .input = '\t', .expected = 1},
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
- STASIS_ASSERT(num_chars(tc[i].data, tc[i].input) == tc[i].expected, "incorrect number of characters detected");
+ const int count = num_chars(tc[i].data, tc[i].input);
+ SYSDEBUG("input[%zu] = '%s'", i, tc[i].data);
+ SYSDEBUG("result[%zu:'%c'] = %d", i, tc[i].input, count);
+ STASIS_ASSERT(count == tc[i].expected, "incorrect number of characters detected");
}
}
@@ -207,6 +215,13 @@ void test_split() {
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
char **result = split((char *) tc[i].data, tc[i].delim, tc[i].max_split);
+ if (tc[i].data) {
+ SYSDEBUG("input[%zu] = %s", i, tc[i].data);
+ for (size_t j = 0; result[j] != NULL; j++) {
+ SYSDEBUG("result[%zu][%zu] = %s", i, j, result[j]);
+ debug_hexdump(result[j], strlen(result[j]) + 1);
+ }
+ }
STASIS_ASSERT(strcmp_array((const char **) result, tc[i].expected) == 0, "Split failed");
guard_array_free(result);
}
@@ -225,8 +240,9 @@ void test_join() {
{.data = NULL, .delim = NULL, .expected = ""},
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
- char *result;
- result = join((char **) tc[i].data, tc[i].delim);
+ char *result = join((char **) tc[i].data, tc[i].delim);
+ SYSDEBUG("result[%zu] = '%s'", i, result ? result : "NULL");
+ debug_hexdump(result, strlen(result ? result : ""));
STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array");
guard_free(result);
}
@@ -245,6 +261,8 @@ void test_join_ex() {
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
char *result = join_ex((char *) tc[i].delim, "a", "b", "c", "d", "e", NULL);
+ SYSDEBUG("result[%zu] = '%s'\n", i, result);
+ debug_hexdump(result, (int) strlen(result) + 1);
STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array");
guard_free(result);
}
@@ -271,6 +289,9 @@ void test_substring_between() {
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
char *result = substring_between((char *) tc[i].data, tc[i].delim);
+ SYSDEBUG("input[%zu] = '%s'", i, tc[i].data ? tc[i].data : "NULL");
+ SYSDEBUG("result[%zu] = '%s'", i, result ? result : "NULL");
+ debug_hexdump(result, result ? strlen(result) + 1 : 0);
STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "unable to extract substring");
guard_free(result);
}
@@ -290,6 +311,16 @@ void test_strdeldup() {
};
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
char **result = strdeldup(tc[i].data);
+ if (tc[i].data && result) {
+ char *input_str = join((char **) tc[i].data, " ");
+ SYSDEBUG("input[%zu] = '%s'", i, input_str);
+ guard_free(input_str);
+
+ char *result_str = join(result, " ");
+ SYSDEBUG("result[%zu] = '%s'", i, result_str);
+ debug_hexdump(result_str, strlen(result_str));
+ guard_free(result_str);
+ }
STASIS_ASSERT(strcmp_array((const char **) result, tc[i].expected) == 0, "incorrect number of duplicates removed");
guard_array_free(result);
}
@@ -316,11 +347,14 @@ void test_lstrip() {
};
STASIS_ASSERT(lstrip(NULL) == NULL, "incorrect return type");
+ const size_t maxlen = 64;
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
- char *buf = calloc(255, sizeof(*buf));
- strncpy(buf, tc[i].data, 254);
- buf[254] = '\0';
+ char *buf = calloc(maxlen + 1, sizeof(*buf));
+ strncpy(buf, tc[i].data, maxlen);
char *result = lstrip(buf);
+ SYSDEBUG("input[%zu] = '%s'", i, buf);
+ SYSDEBUG("result[%zu] = '%s'", i, result ? result : "NULL");
+ debug_hexdump(result, maxlen);
STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-left");
guard_free(buf);
}
@@ -341,11 +375,14 @@ void test_strip() {
};
STASIS_ASSERT(strip(NULL) == NULL, "incorrect return type");
+ const ssize_t maxlen = 64;
for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) {
- char *buf = calloc(255, sizeof(*buf));
- strncpy(buf, tc[i].data, 254);
- buf[254] = '\0';
+ char *buf = calloc(maxlen + 1, sizeof(*buf));
+ strncpy(buf, tc[i].data, maxlen);
char *result = strip(buf);
+ SYSDEBUG("input[%zu] = '%s'", i, buf);
+ SYSDEBUG("result[%zu] = '%s'", i, result ? result : "NULL");
+ debug_hexdump(result, maxlen);
STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-right");
guard_free(buf);
}
diff --git a/tests/test_utils.c b/tests/test_utils.c
index fc53f53..e1689dd 100644
--- a/tests/test_utils.c
+++ b/tests/test_utils.c
@@ -456,6 +456,52 @@ void test_pushd_popd_suggested_workflow() {
}
}
+void test_is_file_compressed() {
+ const char *filenames[] = {
+ "zstd", "bz2", "gz", "xz", "zip",
+ };
+ char datadir[PATH_MAX] = {0};
+ snprintf(datadir, sizeof(datadir), "%s/compression", TEST_DATA_DIR);
+
+ char inputfile[PATH_MAX] = {0};
+ for (size_t i = 0; i < sizeof(filenames) / sizeof(*filenames); i++) {
+ snprintf(inputfile, sizeof(inputfile), "%s/%s", datadir, filenames[i]);
+ const int compressed = is_file_compressed(inputfile);
+ SYSDEBUG("[%zu] is %s compressed? => %s", i, inputfile, compressed ? "Yes" : "No");
+ STASIS_ASSERT(compressed == true, "compression should have been detected");
+ }
+
+ snprintf(inputfile, sizeof(inputfile), "%s/none", datadir);
+ STASIS_ASSERT(is_file_compressed(inputfile) == false, "'none' file should not be detected as compressed data");
+
+ for (size_t i = 0; i < sizeof(filenames) / sizeof(*filenames); i++) {
+ char bytes[128];
+ if (get_random_bytes(bytes, sizeof(bytes))) {
+ SYSERROR("get_random_bytes failed: %s, %s", bytes, strerror(errno));
+ STASIS_ASSERT_FATAL(false, "get_random_bytes failed");
+ return;
+ }
+
+ FILE *fp = fopen(filenames[i], "wb");
+ if (!fp) {
+ SYSERROR("fopen failed: %s, %s", filenames[i], strerror(errno));
+ STASIS_ASSERT_FATAL(false, "fopen failed");
+ return;
+ }
+
+ bytes[0] = 'J';
+ const size_t bytes_written = fwrite(bytes, 1, sizeof(bytes), fp);
+ if (bytes_written != sizeof(bytes)) {
+ SYSERROR("fwrite failed: %s, %s", bytes, strerror(errno));
+ STASIS_ASSERT_FATAL(false, "fwrite failed");
+ return;
+ }
+ fclose(fp);
+
+ STASIS_ASSERT(is_file_compressed(filenames[i]) == false, "random data should not be detected as compressed");
+ }
+}
+
int main(int argc, char *argv[]) {
STASIS_TEST_BEGIN_MAIN();
@@ -479,6 +525,7 @@ int main(int argc, char *argv[]) {
test_dirstack,
test_pushd_popd,
test_pushd_popd_suggested_workflow,
+ test_is_file_compressed,
};
const char *ws = "workspace";
getcwd(cwd_start, sizeof(cwd_start) - 1);
diff --git a/tests/test_wheel.c b/tests/test_wheel.c
index e486b05..c9aff6a 100644
--- a/tests/test_wheel.c
+++ b/tests/test_wheel.c
@@ -175,17 +175,19 @@ int main(int argc, char *argv[]) {
delivery_get_conda_installer_url(&ctx, install_url, PATH_MAX);
delivery_get_conda_installer(&ctx, install_url);
delivery_install_conda(ctx.conda.installer_path, ctx.storage.conda_install_prefix);
+ delivery_conda_enable(&ctx);
guard_free(install_url);
if (conda_activate(ctx.storage.conda_install_prefix, "base")) {
SYSERROR("conda_activate failed");
exit(1);
}
- if (conda_exec("install -y boa conda-build")) {
+ if (conda_exec("install -y conda-build")) {
SYSERROR("conda_exec failed");
exit(1);
}
- if (conda_setup_headless()) {
+
+ if (conda_setup_headless(&ctx.conda.capabilities)) {
SYSERROR("conda_setup_headless failed");
exit(1);
}