aboutsummaryrefslogtreecommitdiff
path: root/src/lib/delivery
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/delivery')
-rw-r--r--src/lib/delivery/CMakeLists.txt1
-rw-r--r--src/lib/delivery/delivery.c290
-rw-r--r--src/lib/delivery/delivery_artifactory.c50
-rw-r--r--src/lib/delivery/delivery_build.c388
-rw-r--r--src/lib/delivery/delivery_conda.c47
-rw-r--r--src/lib/delivery/delivery_docker.c44
-rw-r--r--src/lib/delivery/delivery_export.c77
-rw-r--r--src/lib/delivery/delivery_init.c165
-rw-r--r--src/lib/delivery/delivery_install.c303
-rw-r--r--src/lib/delivery/delivery_populate.c105
-rw-r--r--src/lib/delivery/delivery_postprocess.c70
-rw-r--r--src/lib/delivery/delivery_show.c12
-rw-r--r--src/lib/delivery/delivery_test.c143
-rw-r--r--src/lib/delivery/include/delivery.h140
14 files changed, 1501 insertions, 334 deletions
diff --git a/src/lib/delivery/CMakeLists.txt b/src/lib/delivery/CMakeLists.txt
index 78ed20f..559b2dc 100644
--- a/src/lib/delivery/CMakeLists.txt
+++ b/src/lib/delivery/CMakeLists.txt
@@ -1,4 +1,5 @@
add_library(stasis_delivery STATIC
+ delivery_export.c
delivery_postprocess.c
delivery_conda.c
delivery_docker.c
diff --git a/src/lib/delivery/delivery.c b/src/lib/delivery/delivery.c
index d480ab4..dc9e2ce 100644
--- a/src/lib/delivery/delivery.c
+++ b/src/lib/delivery/delivery.c
@@ -1,8 +1,203 @@
#include "delivery.h"
+#include "conda.h"
+
+struct Delivery *delivery_duplicate(struct Delivery *ctx) {
+ struct Delivery *result = calloc(1, sizeof(*result));
+ if (!result) {
+ return NULL;
+ }
+ // Conda
+ result->conda.conda_packages = strlist_copy(ctx->conda.conda_packages);
+ result->conda.conda_packages_defer = strlist_copy(ctx->conda.conda_packages_defer);
+ result->conda.conda_packages_purge = strlist_copy(ctx->conda.conda_packages_purge);
+ result->conda.pip_packages = strlist_copy(ctx->conda.pip_packages);
+ result->conda.pip_packages_defer = strlist_copy(ctx->conda.pip_packages_defer);
+ result->conda.pip_packages_purge = strlist_copy(ctx->conda.pip_packages_purge);
+ result->conda.wheels_packages = strlist_copy(ctx->conda.wheels_packages);
+ result->conda.installer_arch = strdup_maybe(ctx->conda.installer_arch);
+ result->conda.installer_baseurl = strdup_maybe(ctx->conda.installer_baseurl);
+ result->conda.installer_name = strdup_maybe(ctx->conda.installer_name);
+ result->conda.installer_path = strdup_maybe(ctx->conda.installer_path);
+ result->conda.installer_platform = strdup_maybe(ctx->conda.installer_platform);
+ result->conda.installer_version = strdup_maybe(ctx->conda.installer_version);
+ result->conda.tool_build_version = strdup_maybe(ctx->conda.tool_build_version);
+ result->conda.tool_version = strdup_maybe(ctx->conda.tool_version);
+
+ // Info
+ result->info.build_name = strdup_maybe(ctx->info.build_name);
+ result->info.build_number = strdup_maybe(ctx->info.build_number);
+ result->info.release_name = strdup_maybe(ctx->info.release_name);
+ result->info.time_info = ctx->info.time_info;
+ result->info.time_now = ctx->info.time_now;
+ result->info.time_str_epoch = strdup_maybe(ctx->info.time_str_epoch);
+
+ // Meta
+ result->meta.name = strdup_maybe(ctx->meta.name);
+ result->meta.based_on = strdup_maybe(ctx->meta.based_on);
+ result->meta.codename = strdup_maybe(ctx->meta.codename);
+ result->meta.mission = strdup_maybe(ctx->meta.mission);
+ result->meta.final = ctx->meta.final;
+ result->meta.python = strdup_maybe(ctx->meta.python);
+ result->meta.python_compact = strdup_maybe(ctx->meta.python_compact);
+ result->meta.rc = ctx->meta.rc;
+ result->meta.version = strdup_maybe(ctx->meta.version);
+
+ // Rules
+ result->rules.build_name_fmt = strdup_maybe(ctx->rules.build_name_fmt);
+ result->rules.build_number_fmt = strdup_maybe(ctx->rules.build_number_fmt);
+ // Unused member?
+ result->rules.enable_final = ctx->rules.enable_final;
+ result->rules.release_fmt = ctx->rules.release_fmt;
+ // TODO: need content duplication function
+ memcpy(&result->rules.content, &ctx->rules.content, sizeof(ctx->rules.content));
+
+ if (ctx->rules._handle) {
+ SYSDEBUG("duplicating INIFILE handle - BEGIN");
+ result->rules._handle = malloc(sizeof(*result->rules._handle));
+ if (!result->rules._handle) {
+ SYSERROR("unable to allocate space for INIFILE handle");
+ SYSERROR("%s", "unable to allocate space for INIFILE handle");
+ delivery_free(ctx);
+ return NULL;
+ }
+ result->rules._handle->section = malloc(ctx->rules._handle->section_count * sizeof(**ctx->rules._handle->section));
+ if (!result->rules._handle->section) {
+ guard_free(result->rules._handle);
+ SYSERROR("unable to allocate space for INIFILE section");
+ SYSERROR("%s", "unable to allocate space for INIFILE section");
+ delivery_free(ctx);
+ return NULL;
+ }
+ memcpy(result->rules._handle, &ctx->rules._handle, sizeof(*ctx->rules._handle));
+ SYSDEBUG("duplicating INIFILE handle - END");
+ }
+
+ // Runtime
+ if (ctx->runtime.environ) {
+ result->runtime.environ = runtime_copy(ctx->runtime.environ->data);
+ }
+
+ // Storage
+ result->storage.tools_dir = strdup_maybe(ctx->storage.tools_dir);
+ result->storage.package_dir = strdup_maybe(ctx->storage.package_dir);
+ result->storage.results_dir = strdup_maybe(ctx->storage.results_dir);
+ result->storage.output_dir = strdup_maybe(ctx->storage.output_dir);
+ result->storage.cfgdump_dir = strdup_maybe(ctx->storage.cfgdump_dir);
+ result->storage.delivery_dir = strdup_maybe(ctx->storage.delivery_dir);
+ result->storage.meta_dir = strdup_maybe(ctx->storage.meta_dir);
+ result->storage.mission_dir = strdup_maybe(ctx->storage.mission_dir);
+ result->storage.root = strdup_maybe(ctx->storage.root);
+ result->storage.tmpdir = strdup_maybe(ctx->storage.tmpdir);
+ result->storage.build_dir = strdup_maybe(ctx->storage.build_dir);
+ result->storage.build_docker_dir = strdup_maybe(ctx->storage.build_docker_dir);
+ result->storage.build_recipes_dir = strdup_maybe(ctx->storage.build_recipes_dir);
+ result->storage.build_sources_dir = strdup_maybe(ctx->storage.build_sources_dir);
+ result->storage.build_testing_dir = strdup_maybe(ctx->storage.build_testing_dir);
+ result->storage.conda_artifact_dir = strdup_maybe(ctx->storage.conda_artifact_dir);
+ result->storage.conda_install_prefix = strdup_maybe(ctx->storage.conda_install_prefix);
+ result->storage.conda_staging_dir = strdup_maybe(ctx->storage.conda_staging_dir);
+ result->storage.conda_staging_url = strdup_maybe(ctx->storage.conda_staging_url);
+ result->storage.docker_artifact_dir = strdup_maybe(ctx->storage.docker_artifact_dir);
+ result->storage.wheel_artifact_dir = strdup_maybe(ctx->storage.wheel_artifact_dir);
+ result->storage.wheel_staging_url = strdup_maybe(ctx->storage.wheel_staging_url);
+
+ result->system.arch = strdup_maybe(ctx->system.arch);
+ if (ctx->system.platform) {
+ result->system.platform = malloc(DELIVERY_PLATFORM_MAX * sizeof(*result->system.platform));
+ if (!result->system.platform) {
+ SYSERROR("unable to allocate space for system platform array");
+ SYSERROR("%s", "unable to allocate space for system platform array");
+ delivery_free(ctx);
+ return NULL;
+ }
+ for (size_t i = 0; i < DELIVERY_PLATFORM_MAX; i++) {
+ result->system.platform[i] = strdup_maybe(ctx->system.platform[i]);
+ if (!result->system.platform[i]) {
+ SYSERROR("%s", "unable to allocate record in system platform array");
+ guard_array_n_free(result->system.platform, DELIVERY_PLATFORM_MAX);
+ delivery_free(ctx);
+ return NULL;
+ }
+ }
+ }
+
+ // Docker
+ result->deploy.docker.build_args = strlist_copy(ctx->deploy.docker.build_args);
+ result->deploy.docker.tags = strlist_copy(ctx->deploy.docker.tags);
+ result->deploy.docker.capabilities = ctx->deploy.docker.capabilities;
+ result->deploy.docker.dockerfile = strdup_maybe(ctx->deploy.docker.dockerfile);
+ result->deploy.docker.image_compression = strdup_maybe(ctx->deploy.docker.image_compression);
+ result->deploy.docker.registry = strdup_maybe(ctx->deploy.docker.registry);
+ result->deploy.docker.test_script = strdup_maybe(ctx->deploy.docker.test_script);
+
+ // Jfrog
+ // TODO: break out into a separate a function
+ for (size_t i = 0; i < sizeof(ctx->deploy.jfrog) / sizeof(ctx->deploy.jfrog[0]); i++) {
+ result->deploy.jfrog[i].dest = strdup_maybe(ctx->deploy.jfrog[i].dest);
+ result->deploy.jfrog[i].files = strlist_copy(ctx->deploy.jfrog[i].files);
+ result->deploy.jfrog[i].repo = strdup_maybe(ctx->deploy.jfrog[i].repo);
+ result->deploy.jfrog[i].upload_ctx.ant = ctx->deploy.jfrog[i].upload_ctx.ant;
+ result->deploy.jfrog[i].upload_ctx.archive = ctx->deploy.jfrog[i].upload_ctx.archive;
+ result->deploy.jfrog[i].upload_ctx.build_name = ctx->deploy.jfrog[i].upload_ctx.build_name;
+ result->deploy.jfrog[i].upload_ctx.build_number = ctx->deploy.jfrog[i].upload_ctx.build_number;
+ result->deploy.jfrog[i].upload_ctx.deb = ctx->deploy.jfrog[i].upload_ctx.deb;
+ result->deploy.jfrog[i].upload_ctx.detailed_summary = ctx->deploy.jfrog[i].upload_ctx.detailed_summary;
+ result->deploy.jfrog[i].upload_ctx.dry_run = ctx->deploy.jfrog[i].upload_ctx.dry_run;
+ result->deploy.jfrog[i].upload_ctx.exclusions = strdup_maybe(ctx->deploy.jfrog[i].upload_ctx.exclusions);
+ result->deploy.jfrog[i].upload_ctx.explode = ctx->deploy.jfrog[i].upload_ctx.explode;
+ result->deploy.jfrog[i].upload_ctx.fail_no_op = ctx->deploy.jfrog[i].upload_ctx.fail_no_op;
+ result->deploy.jfrog[i].upload_ctx.flat = ctx->deploy.jfrog[i].upload_ctx.flat;
+ result->deploy.jfrog[i].upload_ctx.include_dirs = ctx->deploy.jfrog[i].upload_ctx.include_dirs;
+ result->deploy.jfrog[i].upload_ctx.module = strdup_maybe(ctx->deploy.jfrog[i].upload_ctx.module);
+ result->deploy.jfrog[i].upload_ctx.project = strdup_maybe(ctx->deploy.jfrog[i].upload_ctx.project);
+ result->deploy.jfrog[i].upload_ctx.quiet = ctx->deploy.jfrog[i].upload_ctx.quiet;
+ result->deploy.jfrog[i].upload_ctx.recursive = ctx->deploy.jfrog[i].upload_ctx.recursive;
+ result->deploy.jfrog[i].upload_ctx.regexp = ctx->deploy.jfrog[i].upload_ctx.regexp;
+ result->deploy.jfrog[i].upload_ctx.retries = ctx->deploy.jfrog[i].upload_ctx.retries;
+ result->deploy.jfrog[i].upload_ctx.retry_wait_time = ctx->deploy.jfrog[i].upload_ctx.retry_wait_time;
+ result->deploy.jfrog[i].upload_ctx.spec = strdup_maybe(ctx->deploy.jfrog[i].upload_ctx.spec);
+ result->deploy.jfrog[i].upload_ctx.spec_vars = strdup_maybe(ctx->deploy.jfrog[i].upload_ctx.spec_vars);
+ result->deploy.jfrog[i].upload_ctx.symlinks = ctx->deploy.jfrog[i].upload_ctx.symlinks;
+ result->deploy.jfrog[i].upload_ctx.sync_deletes = ctx->deploy.jfrog[i].upload_ctx.sync_deletes;
+ result->deploy.jfrog[i].upload_ctx.target_props = strdup_maybe(ctx->deploy.jfrog[i].upload_ctx.target_props);
+ result->deploy.jfrog[i].upload_ctx.threads = ctx->deploy.jfrog[i].upload_ctx.threads;
+ result->deploy.jfrog[i].upload_ctx.workaround_parent_only = ctx->deploy.jfrog[i].upload_ctx.workaround_parent_only;
+ }
+
+ result->deploy.jfrog_auth.access_token = strdup_maybe(ctx->deploy.jfrog_auth.access_token);
+ result->deploy.jfrog_auth.client_cert_key_path = strdup_maybe(ctx->deploy.jfrog_auth.client_cert_key_path);
+ result->deploy.jfrog_auth.client_cert_path = strdup_maybe(ctx->deploy.jfrog_auth.client_cert_path);
+ result->deploy.jfrog_auth.insecure_tls = ctx->deploy.jfrog_auth.insecure_tls;
+ result->deploy.jfrog_auth.password = strdup_maybe(ctx->deploy.jfrog_auth.password);
+ result->deploy.jfrog_auth.server_id = strdup_maybe(ctx->deploy.jfrog_auth.server_id);
+ result->deploy.jfrog_auth.ssh_key_path = strdup_maybe(ctx->deploy.jfrog_auth.ssh_key_path);
+ result->deploy.jfrog_auth.ssh_passphrase = strdup_maybe(ctx->deploy.jfrog_auth.ssh_passphrase);
+ result->deploy.jfrog_auth.url = strdup_maybe(ctx->deploy.jfrog_auth.url);
+ result->deploy.jfrog_auth.user = strdup_maybe(ctx->deploy.jfrog_auth.user);
+
+ for (size_t i = 0; result->tests && i < result->tests->num_used; i++) {
+ result->tests->test[i]->disable = ctx->tests->test[i]->disable;
+ result->tests->test[i]->parallel = ctx->tests->test[i]->parallel;
+ result->tests->test[i]->build_recipe = strdup_maybe(ctx->tests->test[i]->build_recipe);
+ result->tests->test[i]->name = strdup_maybe(ctx->tests->test[i]->name);
+ result->tests->test[i]->version = strdup_maybe(ctx->tests->test[i]->version);
+ result->tests->test[i]->repository = strdup_maybe(ctx->tests->test[i]->repository);
+ result->tests->test[i]->repository_info_ref = strdup_maybe(ctx->tests->test[i]->repository_info_ref);
+ result->tests->test[i]->repository_info_tag = strdup_maybe(ctx->tests->test[i]->repository_info_tag);
+ result->tests->test[i]->repository_remove_tags = strlist_copy(ctx->tests->test[i]->repository_remove_tags);
+ if (ctx->tests->test[i]->runtime->environ) {
+ result->tests->test[i]->runtime->environ = runtime_copy(ctx->tests->test[i]->runtime->environ->data);
+ }
+ result->tests->test[i]->script = strdup_maybe(ctx->tests->test[i]->script);
+ result->tests->test[i]->script_setup = strdup_maybe(ctx->tests->test[i]->script_setup);
+ }
+
+ return result;
+}
void delivery_free(struct Delivery *ctx) {
guard_free(ctx->system.arch);
- guard_array_free(ctx->system.platform);
+ guard_array_n_free(ctx->system.platform, DELIVERY_PLATFORM_MAX);
guard_free(ctx->meta.name);
guard_free(ctx->meta.version);
guard_free(ctx->meta.codename);
@@ -57,23 +252,14 @@ void delivery_free(struct Delivery *ctx) {
guard_strlist_free(&ctx->conda.pip_packages_purge);
guard_strlist_free(&ctx->conda.wheels_packages);
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
- guard_free(ctx->tests[i].name);
- guard_free(ctx->tests[i].version);
- guard_free(ctx->tests[i].repository);
- guard_free(ctx->tests[i].repository_info_ref);
- guard_free(ctx->tests[i].repository_info_tag);
- guard_strlist_free(&ctx->tests[i].repository_remove_tags);
- guard_free(ctx->tests[i].script);
- guard_free(ctx->tests[i].script_setup);
- guard_free(ctx->tests[i].build_recipe);
- // test-specific runtime variables
- guard_runtime_free(ctx->tests[i].runtime.environ);
- }
+ tests_free(&ctx->tests);
guard_free(ctx->rules.release_fmt);
guard_free(ctx->rules.build_name_fmt);
guard_free(ctx->rules.build_number_fmt);
+ if (ctx->rules._handle) {
+ ini_free(&ctx->rules._handle);
+ }
guard_free(ctx->deploy.docker.test_script);
guard_free(ctx->deploy.docker.registry);
@@ -104,8 +290,11 @@ void delivery_free(struct Delivery *ctx) {
guard_free(ctx->_stasis_ini_fp.mission_path);
}
-int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt) {
- size_t fmt_len = strlen(fmt);
+int delivery_format_str(struct Delivery *ctx, char **dest, size_t maxlen, const char *fmt) {
+ const size_t fmt_len = strlen(fmt);
+ if (maxlen < 1) {
+ maxlen = 1;
+ }
if (!*dest) {
*dest = calloc(STASIS_NAME_MAX, sizeof(**dest));
@@ -119,47 +308,47 @@ int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt) {
i++;
switch (fmt[i]) {
case 'n': // name
- strcat(*dest, ctx->meta.name);
+ strncat(*dest, ctx->meta.name, maxlen - 1);
break;
case 'c': // codename
- strcat(*dest, ctx->meta.codename);
+ strncat(*dest, ctx->meta.codename, maxlen - 1);
break;
case 'm': // mission
- strcat(*dest, ctx->meta.mission);
+ strncat(*dest, ctx->meta.mission, maxlen - 1);
break;
case 'r': // revision
- sprintf(*dest + strlen(*dest), "%d", ctx->meta.rc);
+ snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%d", ctx->meta.rc);
break;
case 'R': // "final"-aware revision
if (ctx->meta.final)
- strcat(*dest, "final");
+ strncat(*dest, "final", maxlen);
else
- sprintf(*dest + strlen(*dest), "%d", ctx->meta.rc);
+ snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%d", ctx->meta.rc);
break;
case 'v': // version
- strcat(*dest, ctx->meta.version);
+ strncat(*dest, ctx->meta.version, maxlen - 1);
break;
case 'P': // python version
- strcat(*dest, ctx->meta.python);
+ strncat(*dest, ctx->meta.python, maxlen - 1);
break;
case 'p': // python version major/minor
- strcat(*dest, ctx->meta.python_compact);
+ strncat(*dest, ctx->meta.python_compact, maxlen - 1);
break;
case 'a': // system architecture name
- strcat(*dest, ctx->system.arch);
+ strncat(*dest, ctx->system.arch, maxlen - 1);
break;
case 'o': // system platform (OS) name
- strcat(*dest, ctx->system.platform[DELIVERY_PLATFORM_RELEASE]);
+ strncat(*dest, ctx->system.platform[DELIVERY_PLATFORM_RELEASE], maxlen - 1);
break;
case 't': // unix epoch
- sprintf(*dest + strlen(*dest), "%ld", ctx->info.time_now);
+ snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%ld", ctx->info.time_now);
break;
default: // unknown formatter, write as-is
- sprintf(*dest + strlen(*dest), "%c%c", fmt[i - 1], fmt[i]);
+ snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%c%c", fmt[i - 1], fmt[i]);
break;
}
} else { // write non-format text
- sprintf(*dest + strlen(*dest), "%c", fmt[i]);
+ snprintf(*dest + strlen(*dest), maxlen - strlen(*dest), "%c", fmt[i]);
}
}
return 0;
@@ -174,15 +363,17 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
if (DEFER_CONDA == type) {
dataptr = ctx->conda.conda_packages;
deferred = ctx->conda.conda_packages_defer;
- strcpy(mode, "conda");
+ strncpy(mode, "conda", sizeof(mode) - 1);
} else if (DEFER_PIP == type) {
dataptr = ctx->conda.pip_packages;
deferred = ctx->conda.pip_packages_defer;
- strcpy(mode, "pip");
+ strncpy(mode, "pip", sizeof(mode) - 1);
} else {
- SYSERROR("BUG: type %d does not map to a supported package manager!\n", type);
+ SYSERROR("BUG: type %d does not map to a supported package manager!", type);
exit(1);
}
+ mode[sizeof(mode) - 1] = '\0';
+
msg(STASIS_MSG_L2, "Filtering %s packages by test definition...\n", mode);
struct StrList *filtered = NULL;
@@ -199,7 +390,7 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
// Compile a list of packages that are *also* to be tested.
char *spec_begin = strpbrk(name, "@~=<>!");
char *spec_end = spec_begin;
- char package_name[255] = {0};
+ char package_name[STASIS_NAME_MAX] = {0};
if (spec_end) {
// A version is present in the package name. Jump past operator(s).
@@ -207,25 +398,37 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
spec_end++;
}
strncpy(package_name, name, spec_begin - name);
+ package_name[spec_begin - name] = '\0';
} else {
strncpy(package_name, name, sizeof(package_name) - 1);
+ package_name[sizeof(package_name) - 1] = '\0';
}
remove_extras(package_name);
msg(STASIS_MSG_L3, "package '%s': ", package_name);
// When spec is present in name, set tests->version to the version detected in the name
- for (size_t x = 0; x < sizeof(ctx->tests) / sizeof(ctx->tests[0]) && ctx->tests[x].name != NULL; x++) {
- struct Test *test = &ctx->tests[x];
- char nametmp[1024] = {0};
+ for (size_t x = 0; x < ctx->tests->num_used; x++) {
+ 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';
+
// Is the [test:NAME] in the package name?
if (!strcmp(nametmp, test->name)) {
// Override test->version when a version is provided by the (pip|conda)_package list item
guard_free(test->version);
if (spec_begin && spec_end) {
- test->version = strdup(spec_end);
+ char *version_at = strrchr(spec_end, '@');
+ if (version_at) {
+ if (strlen(version_at)) {
+ version_at++;
+ }
+ test->version = strdup(version_at);
+ } else {
+ test->version = strdup(spec_end);
+ }
} else {
// There are too many possible default branches nowadays: master, main, develop, xyz, etc.
// HEAD is a safe bet.
@@ -233,6 +436,9 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
}
// Is the list item a git+schema:// URL?
+ // TODO: nametmp is just the name so this will never work. but do we want it to? this looks like
+ // TODO: an unsafe feature. We shouldn't be able to change what's in the config. we should
+ // TODO: be getting what we asked for, or exit the program with an error.
if (strstr(nametmp, "git+") && strstr(nametmp, "://")) {
char *xrepo = strstr(nametmp, "+");
if (xrepo) {
@@ -252,13 +458,13 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
int upstream_exists = 0;
if (DEFER_PIP == type) {
- upstream_exists = pkg_index_provides(PKG_USE_PIP, PYPI_INDEX_DEFAULT, name);
+ upstream_exists = pkg_index_provides(PKG_USE_PIP, PYPI_INDEX_DEFAULT, name, ctx->storage.tmpdir);
} else if (DEFER_CONDA == type) {
- upstream_exists = pkg_index_provides(PKG_USE_CONDA, NULL, name);
+ upstream_exists = pkg_index_provides(PKG_USE_CONDA, NULL, name, ctx->storage.tmpdir);
}
if (PKG_INDEX_PROVIDES_FAILED(upstream_exists)) {
- fprintf(stderr, "%s's existence command failed for '%s': %s\n",
+ SYSERROR("%s's existence command failed for '%s': %s",
mode, name, pkg_index_provides_strerror(upstream_exists));
exit(1);
}
@@ -283,7 +489,7 @@ void delivery_defer_packages(struct Delivery *ctx, int type) {
}
if (!strlist_count(deferred)) {
- msg(STASIS_MSG_WARN | STASIS_MSG_L2, "No %s packages were filtered by test definitions\n", mode);
+ SYSWARN("No %s packages were filtered by test definitions", mode);
} else {
if (DEFER_CONDA == type) {
guard_strlist_free(&ctx->conda.conda_packages);
diff --git a/src/lib/delivery/delivery_artifactory.c b/src/lib/delivery/delivery_artifactory.c
index 97db752..d7a5457 100644
--- a/src/lib/delivery/delivery_artifactory.c
+++ b/src/lib/delivery/delivery_artifactory.c
@@ -4,8 +4,12 @@ int delivery_init_artifactory(struct Delivery *ctx) {
int status = 0;
char dest[PATH_MAX] = {0};
char filepath[PATH_MAX] = {0};
- snprintf(dest, sizeof(dest) - 1, "%s/bin", ctx->storage.tools_dir);
- snprintf(filepath, sizeof(dest) - 1, "%s/bin/jf", ctx->storage.tools_dir);
+
+ SYSDEBUG("Initializing artifactory tools");
+ snprintf(dest, sizeof(dest), "%s/bin", ctx->storage.tools_dir);
+ SYSDEBUG("dest=%s", dest);
+ snprintf(filepath, sizeof(dest), "%s/bin/jf", ctx->storage.tools_dir);
+ SYSDEBUG("filepath=%s", filepath);
if (!access(filepath, F_OK)) {
// already have it
@@ -13,6 +17,7 @@ int delivery_init_artifactory(struct Delivery *ctx) {
goto delivery_init_artifactory_envsetup;
}
+ SYSDEBUG("Assign platform");
char *platform = ctx->system.platform[DELIVERY_PLATFORM];
msg(STASIS_MSG_L3, "Downloading %s for %s %s\n", globals.jfrog.remote_filename, platform, ctx->system.arch);
if ((status = artifactory_download_cli(dest,
@@ -32,7 +37,7 @@ int delivery_init_artifactory(struct Delivery *ctx) {
// JFROG_CLI_HOME_DIR is where .jfrog is stored
char path[PATH_MAX] = {0};
- snprintf(path, sizeof(path) - 1, "%s/.jfrog", ctx->storage.build_dir);
+ snprintf(path, sizeof(path), "%s/.jfrog", ctx->storage.build_dir);
setenv("JFROG_CLI_HOME_DIR", path, 1);
// JFROG_CLI_TEMP_DIR is where the obvious is stored
@@ -44,7 +49,7 @@ int delivery_artifact_upload(struct Delivery *ctx) {
int status = 0;
if (jfrt_auth_init(&ctx->deploy.jfrog_auth)) {
- fprintf(stderr, "Failed to initialize Artifactory authentication context\n");
+ SYSERROR("Failed to initialize Artifactory authentication context");
return -1;
}
@@ -55,9 +60,10 @@ int delivery_artifact_upload(struct Delivery *ctx) {
jfrt_upload_init(&ctx->deploy.jfrog[i].upload_ctx);
if (!globals.jfrog.repo) {
- msg(STASIS_MSG_WARN, "Artifactory repository path is not configured!\n");
- fprintf(stderr, "set STASIS_JF_REPO environment variable...\nOr append to configuration file:\n\n");
- fprintf(stderr, "[deploy:artifactory]\nrepo = example/generic/repo/path\n\n");
+ SYSWARN("Artifactory repository path is not configured!");
+ SYSWARN("set STASIS_JF_REPO environment variable...\nOr append to configuration file:");
+ SYSWARN("");
+ SYSWARN("[deploy:artifactory]\nrepo = example/generic/repo/path\n");
status++;
break;
} else if (!ctx->deploy.jfrog[i].repo) {
@@ -66,7 +72,7 @@ int delivery_artifact_upload(struct Delivery *ctx) {
if (!ctx->deploy.jfrog[i].repo || isempty(ctx->deploy.jfrog[i].repo) || !strlen(ctx->deploy.jfrog[i].repo)) {
// Unlikely to trigger if the config parser is working correctly
- msg(STASIS_MSG_ERROR, "Artifactory repository path is empty. Cannot continue.\n");
+ SYSERROR("Artifactory repository path is empty. Cannot continue.");
status++;
break;
}
@@ -76,7 +82,7 @@ int delivery_artifact_upload(struct Delivery *ctx) {
ctx->deploy.jfrog[i].upload_ctx.build_number = ctx->info.build_number;
if (jfrog_cli_rt_ping(&ctx->deploy.jfrog_auth)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Unable to contact artifactory server: %s\n", ctx->deploy.jfrog_auth.url);
+ SYSERROR("Unable to contact artifactory server: %s", ctx->deploy.jfrog_auth.url);
return -1;
}
@@ -84,8 +90,8 @@ int delivery_artifact_upload(struct Delivery *ctx) {
for (size_t f = 0; f < strlist_count(ctx->deploy.jfrog[i].files); f++) {
char dest[PATH_MAX] = {0};
char files[PATH_MAX] = {0};
- snprintf(dest, sizeof(dest) - 1, "%s/%s", ctx->deploy.jfrog[i].repo, ctx->deploy.jfrog[i].dest);
- snprintf(files, sizeof(files) - 1, "%s", strlist_item(ctx->deploy.jfrog[i].files, f));
+ snprintf(dest, sizeof(dest), "%s/%s", ctx->deploy.jfrog[i].repo, ctx->deploy.jfrog[i].dest);
+ snprintf(files, sizeof(files), "%s", strlist_item(ctx->deploy.jfrog[i].files, f));
status += jfrog_cli_rt_upload(&ctx->deploy.jfrog_auth, &ctx->deploy.jfrog[i].upload_ctx, files, dest);
}
}
@@ -99,7 +105,7 @@ int delivery_artifact_upload(struct Delivery *ctx) {
ctx->deploy.jfrog[0].upload_ctx.build_number);
}
} else {
- msg(STASIS_MSG_WARN | STASIS_MSG_L2, "Artifactory build info upload is disabled by CLI argument\n");
+ SYSWARN("Artifactory build info upload is disabled by CLI argument");
}
return status;
@@ -107,7 +113,7 @@ int delivery_artifact_upload(struct Delivery *ctx) {
int delivery_mission_render_files(struct Delivery *ctx) {
if (!ctx->storage.mission_dir) {
- fprintf(stderr, "Mission directory is not configured. Context not initialized?\n");
+ SYSERROR("Mission directory is not configured. Context not initialized?");
return -1;
}
struct Data {
@@ -119,7 +125,7 @@ int delivery_mission_render_files(struct Delivery *ctx) {
memset(&data, 0, sizeof(data));
data.src = calloc(PATH_MAX, sizeof(*data.src));
if (!data.src) {
- perror("data.src");
+ SYSERROR("unable to allocate memory for data.src: %s", strerror(errno));
return -1;
}
@@ -134,7 +140,7 @@ int delivery_mission_render_files(struct Delivery *ctx) {
guard_free(data.src);
return 1;
}
- sprintf(data.src, "%s/%s/%s", ctx->storage.mission_dir, ctx->meta.mission, val.as_char_p);
+ snprintf(data.src, PATH_MAX, "%s/%s/%s", ctx->storage.mission_dir, ctx->meta.mission, val.as_char_p);
msg(STASIS_MSG_L2, "%s\n", data.src);
int err = 0;
@@ -149,14 +155,14 @@ int delivery_mission_render_files(struct Delivery *ctx) {
char *contents = calloc(st.st_size + 1, sizeof(*contents));
if (!contents) {
- perror("template file contents");
+ SYSERROR("unable to allocate memory for template file contents: %s", strerror(errno));
guard_free(data.dest);
continue;
}
FILE *fp = fopen(data.src, "rb");
if (!fp) {
- perror(data.src);
+ SYSERROR("unable to open source template file: %s", strerror(errno));
guard_free(contents);
guard_free(data.dest);
continue;
@@ -189,13 +195,13 @@ int delivery_series_sync(struct Delivery *ctx) {
struct JFRT_Download dl = {0};
if (jfrt_auth_init(&ctx->deploy.jfrog_auth)) {
- fprintf(stderr, "Failed to initialize Artifactory authentication context\n");
+ SYSERROR("Failed to initialize Artifactory authentication context");
return -1; // error
}
char *r_fmt = strdup(ctx->rules.release_fmt);
if (!r_fmt) {
- SYSERROR("%s", "Unable to allocate bytes for release format string");
+ SYSERROR("Unable to allocate bytes for release format string");
return -1;
}
@@ -210,7 +216,7 @@ int delivery_series_sync(struct Delivery *ctx) {
}
char *release_pattern = NULL;
- if (delivery_format_str(ctx, &release_pattern, r_fmt) < 0) {
+ if (delivery_format_str(ctx, &release_pattern, STASIS_NAME_MAX, r_fmt) < 0) {
SYSERROR("Unable to render delivery format string: %s", r_fmt);
guard_free(r_fmt);
return -1;
@@ -223,7 +229,7 @@ int delivery_series_sync(struct Delivery *ctx) {
ctx->meta.mission,
ctx->info.build_name,
release_pattern) < 0) {
- SYSERROR("%s", "Unable to allocate bytes for remote directory path");
+ SYSERROR("Unable to allocate bytes for remote directory path");
guard_free(release_pattern);
return -1;
}
@@ -231,7 +237,7 @@ int delivery_series_sync(struct Delivery *ctx) {
char *dest_dir = NULL;
if (asprintf(&dest_dir, "%s/{1}", ctx->storage.output_dir) < 0) {
- SYSERROR("%s", "Unable to allocate bytes for destination directory path");
+ SYSERROR("Unable to allocate bytes for destination directory path");
return -1;
}
diff --git a/src/lib/delivery/delivery_build.c b/src/lib/delivery/delivery_build.c
index 2d891d2..66f9126 100644
--- a/src/lib/delivery/delivery_build.c
+++ b/src/lib/delivery/delivery_build.c
@@ -1,43 +1,68 @@
+#include <fnmatch.h>
+
#include "delivery.h"
+#include "conda.h"
+#include "recipe.h"
int delivery_build_recipes(struct Delivery *ctx) {
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
+ for (size_t i = 0; i < ctx->tests->num_used; i++) {
char *recipe_dir = NULL;
- if (ctx->tests[i].build_recipe) { // build a conda recipe
- if (recipe_clone(ctx->storage.build_recipes_dir, ctx->tests[i].build_recipe, NULL, &recipe_dir)) {
- fprintf(stderr, "Encountered an issue while cloning recipe for: %s\n", ctx->tests[i].name);
+ if (ctx->tests->test[i]->build_recipe) { // build a conda recipe
+ if (recipe_clone(ctx->storage.build_recipes_dir, ctx->tests->test[i]->build_recipe, NULL, &recipe_dir)) {
+ SYSERROR("Encountered an issue while cloning recipe for: %s", ctx->tests->test[i]->name);
return -1;
}
if (!recipe_dir) {
- fprintf(stderr, "BUG: recipe_clone() succeeded but recipe_dir is NULL: %s\n", strerror(errno));
+ SYSERROR("BUG: recipe_clone() succeeded but recipe_dir is NULL: %s", strerror(errno));
return -1;
}
int recipe_type = recipe_get_type(recipe_dir);
if(!pushd(recipe_dir)) {
if (RECIPE_TYPE_ASTROCONDA == recipe_type) {
- pushd(path_basename(ctx->tests[i].repository));
+ pushd(path_basename(ctx->tests->test[i]->repository));
} else if (RECIPE_TYPE_CONDA_FORGE == recipe_type) {
pushd("recipe");
}
- char recipe_version[100];
- char recipe_buildno[100];
+ char recipe_version[200];
+ char recipe_buildno[200];
char recipe_git_url[PATH_MAX];
char recipe_git_rev[PATH_MAX];
+ char tag[100] = {0};
+ if (ctx->tests->test[i]->repository_info_tag) {
+ const int is_long_tag = num_chars(ctx->tests->test[i]->repository_info_tag, '-') > 1;
+ if (is_long_tag) {
+ const size_t len = strcspn(ctx->tests->test[i]->repository_info_tag, "-");
+ strncpy(tag, ctx->tests->test[i]->repository_info_tag, len);
+ tag[len] = '\0';
+ } else {
+ strncpy(tag, ctx->tests->test[i]->repository_info_tag, sizeof(tag) - 1);
+ tag[sizeof(tag) - 1] = '\0';
+ }
+ } else {
+ strncpy(tag, ctx->tests->test[i]->version, sizeof(tag) - 1);
+ tag[sizeof(tag) - 1] = '\0';
+ }
+
//sprintf(recipe_version, "{%% set version = GIT_DESCRIBE_TAG ~ \".dev\" ~ GIT_DESCRIBE_NUMBER ~ \"+\" ~ GIT_DESCRIBE_HASH %%}");
- //sprintf(recipe_git_url, " git_url: %s", ctx->tests[i].repository);
- //sprintf(recipe_git_rev, " git_rev: %s", ctx->tests[i].version);
+ //sprintf(recipe_git_url, " git_url: %s", ctx->tests->test[i]->repository);
+ //sprintf(recipe_git_rev, " git_rev: %s", ctx->tests->test[i]->version);
// TODO: Conditionally download archives if github.com is the origin. Else, use raw git_* keys ^^^
- sprintf(recipe_version, "{%% set version = \"%s\" %%}", ctx->tests[i].repository_info_tag ? ctx->tests[i].repository_info_tag : ctx->tests[i].version);
- sprintf(recipe_git_url, " url: %s/archive/refs/tags/{{ version }}.tar.gz", ctx->tests[i].repository);
- strcpy(recipe_git_rev, "");
- sprintf(recipe_buildno, " number: 0");
+ // 03/2026 - How can we know if the repository URL supports archive downloads?
+ // Perhaps we can key it to the recipe type, because the archive is a requirement imposed
+ // by conda-forge. Hmm.
+
+ snprintf(recipe_version, sizeof(recipe_version), "{%% set version = \"%s\" %%}", tag);
+ snprintf(recipe_git_url, sizeof(recipe_git_url), " url: %s/archive/refs/tags/{{ version }}.tar.gz", ctx->tests->test[i]->repository);
+ strncpy(recipe_git_rev, "", sizeof(recipe_git_rev) - 1);
+ recipe_git_rev[sizeof(recipe_git_rev) - 1] = '\0';
+ 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
- sprintf(recipe_version, "{%% set version = \"%s\" %%}", ctx->tests[i].version);
+ 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
@@ -55,25 +80,28 @@ int delivery_build_recipes(struct Delivery *ctx) {
char arch[STASIS_NAME_MAX] = {0};
char platform[STASIS_NAME_MAX] = {0};
- strcpy(platform, ctx->system.platform[DELIVERY_PLATFORM]);
+ strncpy(platform, ctx->system.platform[DELIVERY_PLATFORM], sizeof(platform) - 1);
if (strstr(platform, "Darwin")) {
memset(platform, 0, sizeof(platform));
- strcpy(platform, "osx");
+ strncpy(platform, "osx", sizeof(platform) - 1);
}
+ platform[sizeof(platform) - 1] = '\0';
tolower_s(platform);
+
if (strstr(ctx->system.arch, "arm64")) {
- strcpy(arch, "arm64");
+ strncpy(arch, "arm64", sizeof(arch) - 1);
} else if (strstr(ctx->system.arch, "64")) {
- strcpy(arch, "64");
+ strncpy(arch, "64", sizeof(arch) - 1);
} else {
- strcat(arch, "32"); // blind guess
+ strncat(arch, "32", sizeof(arch) - strlen(arch) - 1); // blind guess
}
+ arch[sizeof(arch) - 1] = '\0';
tolower_s(arch);
- sprintf(command, "mambabuild --python=%s -m ../.ci_support/%s_%s_.yaml .",
+ snprintf(command, sizeof(command), "mambabuild --python=%s -m ../.ci_support/%s_%s_.yaml .",
ctx->meta.python, platform, arch);
} else {
- sprintf(command, "mambabuild --python=%s .", ctx->meta.python);
+ snprintf(command, sizeof(command), "mambabuild --python=%s .", ctx->meta.python);
}
int status = conda_exec(command);
if (status) {
@@ -86,7 +114,7 @@ int delivery_build_recipes(struct Delivery *ctx) {
}
popd();
} else {
- fprintf(stderr, "Unable to enter recipe directory %s: %s\n", recipe_dir, strerror(errno));
+ SYSERROR("Unable to enter recipe directory %s: %s", recipe_dir, strerror(errno));
guard_free(recipe_dir);
return -1;
}
@@ -112,7 +140,7 @@ int filter_repo_tags(char *repo, struct StrList *patterns) {
int match = fnmatch(pattern, tag, 0);
if (!match) {
char cmd[PATH_MAX] = {0};
- sprintf(cmd, "git tag -d %s", tag);
+ snprintf(cmd, sizeof(cmd), "git tag -d %s", tag);
result += system(cmd);
break;
}
@@ -127,13 +155,238 @@ int filter_repo_tags(char *repo, struct StrList *patterns) {
return result;
}
+static int read_without_line_endings(const size_t line, char ** arg) {
+ (void) line;
+ if (*arg) {
+ strip(*arg);
+ if (isempty(*arg)) {
+ return 1; // skip
+ }
+ }
+ return 0;
+}
+
+int manylinux_exec(const char *image, const char *script, const char *copy_to_container_dir, const char *copy_from_container_dir, const char *copy_to_host_dir) {
+ int result = -1; // fail by default
+ char *container_name = NULL;
+ char *source_copy_command = NULL;
+ char *copy_command = NULL;
+ char *rm_command = NULL;
+ char *nop_create_command = NULL;
+ char *nop_rm_command = NULL;
+ char *volume_rm_command = NULL;
+ char *find_command = NULL;
+ char *wheel_paths_filename = NULL;
+ char *args = NULL;
+
+ const uid_t uid = geteuid();
+ char suffix[7] = {0};
+
+ // setup
+
+ if (get_random_bytes(suffix, sizeof(suffix))) {
+ SYSERROR("unable to acquire value from random generator");
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&container_name, "manylinux_build_%d_%zd_%s", uid, time(NULL), suffix) < 0) {
+ SYSERROR("unable to allocate memory for container name");
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&args, "--name %s -w /build -v %s:/build", container_name, container_name) < 0) {
+ SYSERROR("unable to allocate memory for docker arguments");
+ goto manylinux_fail;
+ }
+
+ if (!strstr(image, "manylinux")) {
+ SYSERROR("expected a manylinux image, but got %s", image);
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&nop_create_command, "run --name nop_%s -v %s:/build busybox", container_name, container_name) < 0) {
+ SYSERROR("unable to allocate memory for nop container command");
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&source_copy_command, "cp %s nop_%s:/build", copy_to_container_dir, container_name) < 0) {
+ SYSERROR("unable to allocate memory for source copy command");
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&nop_rm_command, "rm nop_%s", container_name) < 0) {
+ SYSERROR("unable to allocate memory for nop container command");
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&wheel_paths_filename, "%s/wheel_paths_%s.txt", globals.tmpdir, container_name) < 0) {
+ SYSERROR("unable to allocate memory for wheel paths file name");
+ goto manylinux_fail;
+ }
+
+ if (asprintf(&find_command, "run --rm -t -v %s:/build busybox sh -c 'find %s -name \"*.whl\"' > %s", container_name, copy_from_container_dir, wheel_paths_filename) < 0) {
+ SYSERROR("unable to allocate memory for find command");
+ goto manylinux_fail;
+ }
+
+ // execute
+
+ if (docker_exec(nop_create_command, 0)) {
+ SYSERROR("docker nop container creation failed");
+ goto manylinux_fail;
+ }
+
+ if (docker_exec(source_copy_command, 0)) {
+ SYSERROR("docker source copy operation failed");
+ goto manylinux_fail;
+ }
+
+ if (docker_exec(nop_rm_command, STASIS_DOCKER_QUIET)) {
+ SYSERROR("docker nop container removal failed");
+ goto manylinux_fail;
+ }
+
+ if (docker_script(image, args, (char *) script, 0)) {
+ SYSERROR("manylinux execution failed");
+ goto manylinux_fail;
+ }
+
+ if (docker_exec(find_command, 0)) {
+ SYSERROR("docker find command failed");
+ goto manylinux_fail;
+ }
+
+ struct StrList *wheel_paths = strlist_init();
+ if (!wheel_paths) {
+ SYSERROR("wheel_paths not initialized");
+ goto manylinux_fail;
+ }
+
+ if (strlist_append_file(wheel_paths, wheel_paths_filename, read_without_line_endings)) {
+ SYSERROR("wheel_paths append failed");
+ goto manylinux_fail;
+ }
+
+ for (size_t i = 0; i < strlist_count(wheel_paths); i++) {
+ const char *item = strlist_item(wheel_paths, i);
+ if (asprintf(&copy_command, "cp %s:%s %s", container_name, item, copy_to_host_dir) < 0) {
+ SYSERROR("unable to allocate memory for docker copy command");
+ goto manylinux_fail;
+ }
+
+ if (docker_exec(copy_command, 0)) {
+ SYSERROR("docker copy operation failed");
+ goto manylinux_fail;
+ }
+ guard_free(copy_command);
+ }
+
+ // Success
+ result = 0;
+
+ manylinux_fail:
+ if (wheel_paths_filename) {
+ remove(wheel_paths_filename);
+ }
+
+ if (container_name) {
+ // Keep going on failure unless memory related.
+ // We don't want build debris everywhere.
+ if (asprintf(&rm_command, "rm %s", container_name) < 0) {
+ SYSERROR("unable to allocate memory for rm command");
+ goto late_fail;
+ }
+
+ if (docker_exec(rm_command, STASIS_DOCKER_QUIET)) {
+ SYSERROR("docker container removal operation failed");
+ }
+
+ if (asprintf(&volume_rm_command, "volume rm -f %s", container_name) < 0) {
+ SYSERROR("unable to allocate memory for docker volume removal command");
+ goto late_fail;
+ }
+
+ if (docker_exec(volume_rm_command, STASIS_DOCKER_QUIET)) {
+ SYSERROR("docker volume removal operation failed");
+ }
+ }
+
+ late_fail:
+ guard_free(container_name);
+ guard_free(args);
+ guard_free(copy_command);
+ guard_free(rm_command);
+ guard_free(volume_rm_command);
+ guard_free(source_copy_command);
+ guard_free(nop_create_command);
+ guard_free(nop_rm_command);
+ guard_free(find_command);
+ guard_free(wheel_paths_filename);
+ guard_strlist_free(&wheel_paths);
+ return result;
+}
+
+int delivery_build_wheels_manylinux(struct Delivery *ctx, const char *outdir) {
+ msg(STASIS_MSG_L1, "Building wheels\n");
+
+ const char *manylinux_image = globals.wheel_builder_manylinux_image;
+ if (!manylinux_image) {
+ SYSERROR("manylinux_image not initialized");
+ return -1;
+ }
+
+ int manylinux_build_status = 0;
+
+ msg(STASIS_MSG_L2, "Using: %s\n", manylinux_image);
+ const struct Meta *meta = &ctx->meta;
+ const char *script_fmt =
+ "set -e -x\n"
+ "git config --global --add safe.directory /build\n"
+ "python%s -m pip install auditwheel build\n"
+ "python%s -m build -w .\n"
+ "auditwheel show --allow-pure-python-wheel dist/*.whl\n"
+ "auditwheel repair --allow-pure-python-wheel dist/*.whl\n";
+ char *script = NULL;
+ if (asprintf(&script, script_fmt,
+ meta->python, meta->python) < 0) {
+ SYSERROR("unable to allocate memory for build script");
+ return -1;
+ }
+ manylinux_build_status = manylinux_exec(
+ manylinux_image,
+ script,
+ "./",
+ "/build/wheelhouse",
+ outdir);
+
+ if (manylinux_build_status) {
+ SYSERROR("manylinux build failed (%d)", manylinux_build_status);
+ guard_free(script);
+ return -1;
+ }
+ guard_free(script);
+ return 0;
+}
+
struct StrList *delivery_build_wheels(struct Delivery *ctx) {
+ const int on_linux = strcmp(ctx->system.platform[DELIVERY_PLATFORM], "Linux") == 0;
+ const int docker_usable = ctx->deploy.docker.capabilities.usable;
+ int use_builder_build = strcmp(globals.wheel_builder, "native") == 0;
+ const int use_builder_cibuildwheel = strcmp(globals.wheel_builder, "cibuildwheel") == 0 && on_linux && docker_usable;
+ const int use_builder_manylinux = strcmp(globals.wheel_builder, "manylinux") == 0 && on_linux && docker_usable;
+
+ if (!use_builder_build && !use_builder_cibuildwheel && !use_builder_manylinux) {
+ SYSWARN("Cannot build wheel for platform using: %s", globals.wheel_builder);
+ SYSWARN("Falling back to native toolchain.", globals.wheel_builder);
+ use_builder_build = 1;
+ }
+
struct StrList *result = NULL;
struct Process proc = {0};
result = strlist_init();
if (!result) {
- perror("unable to allocate memory for string list");
+ SYSERROR("unable to allocate memory for string list");
return NULL;
}
@@ -141,57 +394,103 @@ struct StrList *delivery_build_wheels(struct Delivery *ctx) {
char name[100] = {0};
char *fullspec = strlist_item(ctx->conda.pip_packages_defer, p);
strncpy(name, fullspec, sizeof(name) - 1);
+ name[sizeof(name) - 1] = '\0';
remove_extras(name);
char *spec = find_version_spec(name);
if (spec) {
*spec = '\0';
}
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
- if ((ctx->tests[i].name && !strcmp(name, ctx->tests[i].name)) && (!ctx->tests[i].build_recipe && ctx->tests[i].repository)) { // build from source
+ for (size_t i = 0; i < ctx->tests->num_used; i++) {
+ if ((ctx->tests->test[i]->name && !strcmp(name, ctx->tests->test[i]->name)) && (!ctx->tests->test[i]->build_recipe && ctx->tests->test[i]->repository)) { // build from source
char srcdir[PATH_MAX];
char wheeldir[PATH_MAX];
memset(srcdir, 0, sizeof(srcdir));
memset(wheeldir, 0, sizeof(wheeldir));
- sprintf(srcdir, "%s/%s", ctx->storage.build_sources_dir, ctx->tests[i].name);
- if (git_clone(&proc, ctx->tests[i].repository, srcdir, ctx->tests[i].version)) {
- SYSERROR("Unable to checkout tag '%s' for package '%s' from repository '%s'\n",
- ctx->tests[i].version, ctx->tests[i].name, ctx->tests[i].repository);
+ snprintf(srcdir, sizeof(srcdir), "%s/%s", ctx->storage.build_sources_dir, ctx->tests->test[i]->name);
+ if (git_clone(&proc, ctx->tests->test[i]->repository, srcdir, ctx->tests->test[i]->version)) {
+ SYSERROR("Unable to checkout tag '%s' for package '%s' from repository '%s'",
+ ctx->tests->test[i]->version, ctx->tests->test[i]->name, ctx->tests->test[i]->repository);
return NULL;
}
- if (ctx->tests[i].repository_remove_tags && strlist_count(ctx->tests[i].repository_remove_tags)) {
- filter_repo_tags(srcdir, ctx->tests[i].repository_remove_tags);
+ if (!ctx->tests->test[i]->repository_info_tag) {
+ ctx->tests->test[i]->repository_info_tag = strdup(git_describe(srcdir));
+ }
+ if (!ctx->tests->test[i]->repository_info_ref) {
+ ctx->tests->test[i]->repository_info_ref = strdup(git_rev_parse(srcdir, ctx->tests->test[i]->version));
+ }
+ if (ctx->tests->test[i]->repository_remove_tags && strlist_count(ctx->tests->test[i]->repository_remove_tags)) {
+ filter_repo_tags(srcdir, ctx->tests->test[i]->repository_remove_tags);
}
if (!pushd(srcdir)) {
char dname[NAME_MAX];
char outdir[PATH_MAX];
- char cmd[PATH_MAX * 2];
+ char *cmd = NULL;
memset(dname, 0, sizeof(dname));
memset(outdir, 0, sizeof(outdir));
- memset(cmd, 0, sizeof(outdir));
- strcpy(dname, ctx->tests[i].name);
+ const int dep_status = check_python_package_dependencies(".");
+ if (dep_status) {
+ SYSERROR("Please replace all occurrences above with standard package specs:\n"
+ "\n"
+ " package==x.y.z\n"
+ " package>=x.y.z\n"
+ " package<=x.y.z\n"
+ " ...\n"
+ "\n");
+ COE_CHECK_ABORT(true, "Unreproducible delivery");
+ }
+
+ strncpy(dname, ctx->tests->test[i]->name, sizeof(dname) - 1);
+ dname[sizeof(dname) - 1] = '\0';
tolower_s(dname);
- sprintf(outdir, "%s/%s", ctx->storage.wheel_artifact_dir, dname);
+ snprintf(outdir, sizeof(outdir), "%s/%s", ctx->storage.wheel_artifact_dir, dname);
if (mkdirs(outdir, 0755)) {
- fprintf(stderr, "failed to create output directory: %s\n", outdir);
+ SYSERROR("failed to create output directory: %s", outdir);
guard_strlist_free(&result);
return NULL;
}
+ if (use_builder_manylinux) {
+ if (delivery_build_wheels_manylinux(ctx, outdir)) {
+ SYSERROR("failed to generate wheel package for %s-%s", ctx->tests->test[i]->name,
+ ctx->tests->test[i]->version);
+ guard_strlist_free(&result);
+ guard_free(cmd);
+ return NULL;
+ }
+ } else if (use_builder_build || use_builder_cibuildwheel) {
+ if (use_builder_build) {
+ if (asprintf(&cmd, "-m build -w -o %s", outdir) < 0) {
+ SYSERROR("Unable to allocate memory for build command");
+ return NULL;
+ }
+ } else if (use_builder_cibuildwheel) {
+ if (asprintf(&cmd, "-m cibuildwheel --output-dir %s --only cp%s-manylinux_%s",
+ outdir, ctx->meta.python_compact, ctx->system.arch) < 0) {
+ SYSERROR("Unable to allocate memory for cibuildwheel command");
+ return NULL;
+ }
+ }
- sprintf(cmd, "-m build -w -o %s", outdir);
- if (python_exec(cmd)) {
- fprintf(stderr, "failed to generate wheel package for %s-%s\n", ctx->tests[i].name,
- ctx->tests[i].version);
- guard_strlist_free(&result);
+ if (python_exec(cmd)) {
+ SYSERROR("failed to generate wheel package for %s-%s", ctx->tests->test[i]->name,
+ ctx->tests->test[i]->version);
+ guard_strlist_free(&result);
+ guard_free(cmd);
+ return NULL;
+ }
+ } else {
+ SYSERROR("unknown wheel builder backend: %s", globals.wheel_builder);
return NULL;
}
+
+ guard_free(cmd);
popd();
} else {
- fprintf(stderr, "Unable to enter source directory %s: %s\n", srcdir, strerror(errno));
+ SYSERROR("Unable to enter source directory %s: %s", srcdir, strerror(errno));
guard_strlist_free(&result);
return NULL;
}
@@ -200,4 +499,3 @@ struct StrList *delivery_build_wheels(struct Delivery *ctx) {
}
return result;
}
-
diff --git a/src/lib/delivery/delivery_conda.c b/src/lib/delivery/delivery_conda.c
index 8974ae8..117e6c9 100644
--- a/src/lib/delivery/delivery_conda.c
+++ b/src/lib/delivery/delivery_conda.c
@@ -1,21 +1,37 @@
#include "delivery.h"
+#include "conda.h"
-void delivery_get_conda_installer_url(struct Delivery *ctx, char *result) {
+void delivery_get_conda_installer_url(struct Delivery *ctx, char *result, size_t maxlen) {
+ int len = 0;
if (ctx->conda.installer_version) {
// Use version specified by configuration file
- sprintf(result, "%s/%s-%s-%s-%s.sh", ctx->conda.installer_baseurl,
+ len = snprintf(NULL, 0, "%s/%s-%s-%s-%s.sh",
+ ctx->conda.installer_baseurl,
+ ctx->conda.installer_name,
+ ctx->conda.installer_version,
+ ctx->conda.installer_platform,
+ ctx->conda.installer_arch);
+
+ snprintf(result, maxlen - len, "%s/%s-%s-%s-%s.sh",
+ ctx->conda.installer_baseurl,
ctx->conda.installer_name,
ctx->conda.installer_version,
ctx->conda.installer_platform,
ctx->conda.installer_arch);
} else {
// Use latest installer
- sprintf(result, "%s/%s-%s-%s.sh", ctx->conda.installer_baseurl,
+ len = snprintf(NULL, 0, "%s/%s-%s-%s.sh",
+ ctx->conda.installer_baseurl,
ctx->conda.installer_name,
ctx->conda.installer_platform,
ctx->conda.installer_arch);
- }
+ snprintf(result, maxlen - len, "%s/%s-%s-%s.sh",
+ ctx->conda.installer_baseurl,
+ ctx->conda.installer_name,
+ ctx->conda.installer_platform,
+ ctx->conda.installer_arch);
+ }
}
int delivery_get_conda_installer(struct Delivery *ctx, char *installer_url) {
@@ -23,12 +39,15 @@ int delivery_get_conda_installer(struct Delivery *ctx, char *installer_url) {
char *installer = path_basename(installer_url);
memset(script_path, 0, sizeof(script_path));
- sprintf(script_path, "%s/%s", ctx->storage.tmpdir, installer);
+ snprintf(script_path, sizeof(script_path), "%s/%s", ctx->storage.tmpdir, installer);
if (access(script_path, F_OK)) {
// Script doesn't exist
- long fetch_status = download(installer_url, script_path, NULL);
+ char *errmsg = NULL;
+ long fetch_status = download(installer_url, script_path, &errmsg);
if (HTTP_ERROR(fetch_status) || fetch_status < 0) {
// download failed
+ SYSERROR("download failed: %s: %s", errmsg, installer_url);
+ guard_free(errmsg);
return -1;
}
} else {
@@ -51,31 +70,31 @@ void delivery_install_conda(char *install_script, char *conda_install_dir) {
if (!access(conda_install_dir, F_OK)) {
// directory exists so remove it
if (rmtree(conda_install_dir)) {
- perror("unable to remove previous installation");
+ SYSERROR("unable to remove previous installation: %s", strerror(errno));
exit(1);
}
// Proceed with the installation
// -b = batch mode (non-interactive)
char cmd[PATH_MAX] = {0};
- snprintf(cmd, sizeof(cmd) - 1, "%s %s -b -p %s",
+ snprintf(cmd, sizeof(cmd), "%s %s -b -p %s",
find_program("bash"),
install_script,
conda_install_dir);
if (shell_safe(&proc, cmd)) {
- fprintf(stderr, "conda installation failed\n");
+ SYSERROR("conda installation failed");
exit(1);
}
} else {
// Proceed with the installation
// -b = batch mode (non-interactive)
char cmd[PATH_MAX] = {0};
- snprintf(cmd, sizeof(cmd) - 1, "%s %s -b -p %s",
+ snprintf(cmd, sizeof(cmd), "%s %s -b -p %s",
find_program("bash"),
install_script,
conda_install_dir);
if (shell_safe(&proc, cmd)) {
- fprintf(stderr, "conda installation failed\n");
+ SYSERROR("conda installation failed");
exit(1);
}
}
@@ -86,7 +105,7 @@ 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")) {
- fprintf(stderr, "conda activation failed\n");
+ SYSERROR("conda activation failed");
exit(1);
}
@@ -94,10 +113,10 @@ void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir) {
// 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];
- sprintf(rcpath, "%s/%s", conda_install_dir, ".condarc");
+ snprintf(rcpath, sizeof(rcpath), "%s/%s", conda_install_dir, ".condarc");
setenv("CONDARC", rcpath, 1);
if (runtime_replace(&ctx->runtime.environ, __environ)) {
- perror("unable to replace runtime environment after activating conda");
+ SYSERROR("unable to replace runtime environment after activating conda");
exit(1);
}
diff --git a/src/lib/delivery/delivery_docker.c b/src/lib/delivery/delivery_docker.c
index 57015ad..79e9729 100644
--- a/src/lib/delivery/delivery_docker.c
+++ b/src/lib/delivery/delivery_docker.c
@@ -11,15 +11,15 @@ int delivery_docker(struct Delivery *ctx) {
size_t total_build_args = strlist_count(ctx->deploy.docker.build_args);
if (!has_registry) {
- msg(STASIS_MSG_WARN | STASIS_MSG_L2, "No docker registry defined. You will need to manually re-tag the resulting image.\n");
+ SYSWARN("No docker registry defined. You will need to manually re-tag the resulting image.");
}
if (!total_tags) {
char default_tag[PATH_MAX];
- msg(STASIS_MSG_WARN | STASIS_MSG_L2, "No docker tags defined by configuration. Generating default tag(s).\n");
+ SYSWARN("No docker tags defined by configuration. Generating default tag(s).");
// generate local tag
memset(default_tag, 0, sizeof(default_tag));
- sprintf(default_tag, "%s:%s-py%s", ctx->meta.name, ctx->info.build_name, ctx->meta.python_compact);
+ snprintf(default_tag, sizeof(default_tag), "%s:%s-py%s", ctx->meta.name, ctx->info.build_name, ctx->meta.python_compact);
tolower_s(default_tag);
// Add tag
@@ -29,7 +29,7 @@ int delivery_docker(struct Delivery *ctx) {
if (has_registry) {
// generate tag for target registry
memset(default_tag, 0, sizeof(default_tag));
- sprintf(default_tag, "%s/%s:%s-py%s", ctx->deploy.docker.registry, ctx->meta.name, ctx->info.build_number, ctx->meta.python_compact);
+ snprintf(default_tag, sizeof(default_tag), "%s/%s:%s-py%s", ctx->deploy.docker.registry, ctx->meta.name, ctx->info.build_number, ctx->meta.python_compact);
tolower_s(default_tag);
// Add tag
@@ -44,9 +44,10 @@ int delivery_docker(struct Delivery *ctx) {
// Append image tags to command
for (size_t i = 0; i < total_tags; i++) {
char *tag_orig = strlist_item(ctx->deploy.docker.tags, i);
- strcpy(tag, tag_orig);
+ strncpy(tag, tag_orig, sizeof(tag) - 1);
+ tag[sizeof(tag) - 1] = '\0';
docker_sanitize_tag(tag);
- sprintf(args + strlen(args), " -t \"%s\" ", tag);
+ snprintf(args + strlen(args), sizeof(args) - strlen(args), " -t \"%s\" ", tag);
}
// Append build arguments to command (i.e. --build-arg "key=value"
@@ -55,7 +56,7 @@ int delivery_docker(struct Delivery *ctx) {
if (!build_arg) {
break;
}
- sprintf(args + strlen(args), " --build-arg \"%s\" ", build_arg);
+ snprintf(args + strlen(args), sizeof(args) - strlen(args), " --build-arg \"%s\" ", build_arg);
}
// Build the image
@@ -65,34 +66,34 @@ int delivery_docker(struct Delivery *ctx) {
memset(delivery_file, 0, sizeof(delivery_file));
memset(dest, 0, sizeof(dest));
- sprintf(delivery_file, "%s/%s.yml", ctx->storage.delivery_dir, ctx->info.release_name);
+ snprintf(delivery_file, sizeof(delivery_file), "%s/%s.yml", ctx->storage.delivery_dir, ctx->info.release_name);
if (access(delivery_file, F_OK) < 0) {
- fprintf(stderr, "docker build cannot proceed without delivery file: %s\n", delivery_file);
+ SYSERROR("docker build cannot proceed without delivery file: %s", delivery_file);
return -1;
}
- sprintf(dest, "%s/%s.yml", ctx->storage.build_docker_dir, ctx->info.release_name);
+ snprintf(dest, sizeof(dest), "%s/%s.yml", ctx->storage.build_docker_dir, ctx->info.release_name);
if (copy2(delivery_file, dest, CT_PERM)) {
- fprintf(stderr, "Failed to copy delivery file to %s: %s\n", dest, strerror(errno));
+ SYSERROR("Failed to copy delivery file to %s: %s", dest, strerror(errno));
return -1;
}
memset(dest, 0, sizeof(dest));
- sprintf(dest, "%s/packages", ctx->storage.build_docker_dir);
+ snprintf(dest, sizeof(dest), "%s/packages", ctx->storage.build_docker_dir);
msg(STASIS_MSG_L2, "Copying conda packages\n");
memset(rsync_cmd, 0, sizeof(rsync_cmd));
- sprintf(rsync_cmd, "rsync -avi --progress '%s' '%s'", ctx->storage.conda_artifact_dir, dest);
+ snprintf(rsync_cmd, sizeof(rsync_cmd), "rsync -avi --progress '%s' '%s'", ctx->storage.conda_artifact_dir, dest);
if (system(rsync_cmd)) {
- fprintf(stderr, "Failed to copy conda artifacts to docker build directory\n");
+ SYSERROR("Failed to copy conda artifacts to docker build directory");
return -1;
}
msg(STASIS_MSG_L2, "Copying wheel packages\n");
memset(rsync_cmd, 0, sizeof(rsync_cmd));
- sprintf(rsync_cmd, "rsync -avi --progress '%s' '%s'", ctx->storage.wheel_artifact_dir, dest);
+ snprintf(rsync_cmd, sizeof(rsync_cmd), "rsync -avi --progress '%s' '%s'", ctx->storage.wheel_artifact_dir, dest);
if (system(rsync_cmd)) {
- fprintf(stderr, "Failed to copy wheel artifacts to docker build directory\n");
+ SYSWARN("Failed to copy wheel artifacts to docker build directory. No wheels produced?");
}
if (docker_build(ctx->storage.build_docker_dir, args, ctx->deploy.docker.capabilities.build)) {
@@ -102,23 +103,24 @@ int delivery_docker(struct Delivery *ctx) {
// Test the image
// All tags point back to the same image so test the first one we see
// regardless of how many are defined
- strcpy(tag, strlist_item(ctx->deploy.docker.tags, 0));
+ strncpy(tag, strlist_item(ctx->deploy.docker.tags, 0), sizeof(tag) - 1);
+ tag[sizeof(tag) - 1] = '\0';
docker_sanitize_tag(tag);
msg(STASIS_MSG_L2, "Executing image test script for %s\n", tag);
if (ctx->deploy.docker.test_script) {
if (isempty(ctx->deploy.docker.test_script)) {
- msg(STASIS_MSG_L2 | STASIS_MSG_WARN, "Image test script has no content\n");
+ SYSWARN("Image test script has no content");
} else {
int state;
- if ((state = docker_script(tag, ctx->deploy.docker.test_script, 0))) {
- msg(STASIS_MSG_L2 | STASIS_MSG_ERROR, "Non-zero exit (%d) from test script. %s image archive will not be generated.\n", state >> 8, tag);
+ if ((state = docker_script(tag, "--rm", ctx->deploy.docker.test_script, 0))) {
+ SYSERROR("Non-zero exit (%d) from test script. %s image archive will not be generated.", state >> 8, tag);
// test failed -- don't save the image
return -1;
}
}
} else {
- msg(STASIS_MSG_L2 | STASIS_MSG_WARN, "No image test script defined\n");
+ SYSWARN("No image test script defined");
}
// Test successful, save image
diff --git a/src/lib/delivery/delivery_export.c b/src/lib/delivery/delivery_export.c
new file mode 100644
index 0000000..0321050
--- /dev/null
+++ b/src/lib/delivery/delivery_export.c
@@ -0,0 +1,77 @@
+#include "delivery.h"
+#include "conda.h"
+
+static void delivery_export_configuration(const struct Delivery *ctx) {
+ msg(STASIS_MSG_L2, "Exporting delivery configuration\n");
+
+ SYSDEBUG("Entering configuration directory: %s", ctx->storage.delivery_dir);
+ if (!pushd(ctx->storage.cfgdump_dir)) {
+ char filename[PATH_MAX] = {0};
+ SYSDEBUG("Populating filename");
+ snprintf(filename, sizeof(filename), "%s.ini", ctx->info.release_name);
+ SYSDEBUG("filename: %s", filename);
+
+ SYSDEBUG("%s: opening", filename);
+ FILE *spec = fopen(filename, "w+");
+ if (!spec) {
+ SYSERROR("open failed %s", filename);
+ exit(1);
+ }
+ SYSDEBUG("%s: writing", filename);
+ ini_write(ctx->_stasis_ini_fp.delivery, &spec, INI_WRITE_RAW);
+ SYSDEBUG("%s: writing done", filename);
+ fclose(spec);
+ SYSDEBUG("%s: closing", filename);
+
+ SYSDEBUG("Zeroing filename");
+ memset(filename, 0, sizeof(filename));
+ SYSDEBUG("Populating rendered filename");
+ snprintf(filename, sizeof(filename), "%s-rendered.ini", ctx->info.release_name);
+ SYSDEBUG("filename: %s", filename);
+
+ SYSDEBUG("%s: opening", filename);
+ spec = fopen(filename, "w+");
+ if (!spec) {
+ SYSERROR("open failed %s", filename);
+ exit(1);
+ }
+ SYSDEBUG("%s: writing", filename);
+ ini_write(ctx->_stasis_ini_fp.delivery, &spec, INI_WRITE_PRESERVE);
+ SYSDEBUG("%s: writing done", filename);
+ SYSDEBUG("%s: closing", filename);
+ fclose(spec);
+ SYSDEBUG("Returning from %s", ctx->storage.cfgdump_dir);
+ popd();
+ } else {
+ SYSERROR("Failed to enter directory: %s", ctx->storage.delivery_dir);
+ exit(1);
+ }
+}
+
+void delivery_export(const struct Delivery *ctx, char *envs[]) {
+ delivery_export_configuration(ctx);
+
+ for (size_t i = 0; envs[i] != NULL; i++) {
+ char *name = envs[i];
+ msg(STASIS_MSG_L2, "Exporting %s\n", name);
+ if (conda_env_export(name, ctx->storage.delivery_dir, name)) {
+ SYSERROR("export failed %s", name);
+ exit(1);
+ }
+ }
+}
+
+void delivery_rewrite_stage1(struct Delivery *ctx, char *specfile) {
+ // Rewrite release environment output (i.e. set package origin(s) to point to the deployment server, etc.)
+ msg(STASIS_MSG_L3, "Rewriting release spec file (stage 1): %s\n", path_basename(specfile));
+ delivery_rewrite_spec(ctx, specfile, DELIVERY_REWRITE_SPEC_STAGE_1);
+
+ msg(STASIS_MSG_L1, "Rendering mission templates\n");
+ delivery_mission_render_files(ctx);
+}
+
+void delivery_rewrite_stage2(struct Delivery *ctx, char *specfile) {
+ msg(STASIS_MSG_L3, "Rewriting release spec file (stage 2): %s\n", path_basename(specfile));
+ delivery_rewrite_spec(ctx, specfile, DELIVERY_REWRITE_SPEC_STAGE_2);
+}
+
diff --git a/src/lib/delivery/delivery_init.c b/src/lib/delivery/delivery_init.c
index 56c591a..5bc326d 100644
--- a/src/lib/delivery/delivery_init.c
+++ b/src/lib/delivery/delivery_init.c
@@ -1,3 +1,6 @@
+#include <fnmatch.h>
+#include <sys/utsname.h>
+
#include "delivery.h"
int has_mount_flags(const char *mount_point, const unsigned long flags) {
@@ -11,34 +14,54 @@ int has_mount_flags(const char *mount_point, const unsigned long flags) {
int delivery_init_tmpdir(struct Delivery *ctx) {
char *tmpdir = NULL;
- char *x = NULL;
- int unusable = 0;
+ int unusable = 1;
errno = 0;
- x = getenv("TMPDIR");
+ //int need_setenv = 0;
+ const char *x = getenv("TMPDIR");
if (x) {
guard_free(ctx->storage.tmpdir);
tmpdir = strdup(x);
+ if (!tmpdir) {
+ // memory error
+ SYSERROR("unable to allocate tmpdir");
+ goto l_delivery_init_tmpdir_fatal;
+ }
} else {
- tmpdir = ctx->storage.tmpdir;
+ tmpdir = strdup("/tmp/stasis");
+ if (!tmpdir) {
+ SYSERROR("unable to allocate tmpdir");
+ goto l_delivery_init_tmpdir_fatal;
+ }
+ //need_setenv = 1;
}
- if (!tmpdir) {
- // memory error
- return -1;
+ if (!ctx->storage.tmpdir) {
+ ctx->storage.tmpdir = strdup(tmpdir);
+ if (!ctx->storage.tmpdir) {
+ SYSERROR("unable to allocate ctx->storage.tmpdir");
+ goto l_delivery_init_tmpdir_fatal;
+ }
+ } else {
+ // we already have a temp directory to use
+ guard_free(tmpdir);
+ tmpdir = strdup(ctx->storage.tmpdir);
+ if (!tmpdir) {
+ SYSERROR("unable to allocate tmpdir");
+ goto l_delivery_init_tmpdir_fatal;
+ }
}
-
// If the directory doesn't exist, create it
if (access(tmpdir, F_OK) < 0) {
if (mkdirs(tmpdir, 0755) < 0) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "Unable to create temporary storage directory: %s (%s)\n", tmpdir, strerror(errno));
+ SYSERROR("Unable to create temporary storage directory: %s (%s)", tmpdir, strerror(errno));
goto l_delivery_init_tmpdir_fatal;
}
}
// If we can't read, write, or execute, then die
if (access(tmpdir, R_OK | W_OK | X_OK) < 0) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "%s requires at least 0755 permissions.\n");
+ SYSERROR("%s requires at least 0755 permissions.");
goto l_delivery_init_tmpdir_fatal;
}
@@ -50,26 +73,38 @@ int delivery_init_tmpdir(struct Delivery *ctx) {
#if defined(STASIS_OS_LINUX)
// If we can't execute programs, or write data to the file system at all, then die
if ((st.f_flag & ST_NOEXEC) != 0) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "%s is mounted with noexec\n", tmpdir);
+ SYSERROR("%s is mounted with noexec", tmpdir);
goto l_delivery_init_tmpdir_fatal;
}
#endif
if ((st.f_flag & ST_RDONLY) != 0) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "%s is mounted read-only\n", tmpdir);
+ SYSERROR("%s is mounted read-only", tmpdir);
goto l_delivery_init_tmpdir_fatal;
}
- if (!globals.tmpdir) {
+ if (!globals.tmpdir || strcmp(globals.tmpdir, ctx->storage.tmpdir) != 0) {
globals.tmpdir = strdup(tmpdir);
+ if (!globals.tmpdir) {
+ SYSERROR("unable to allocate globals.tmpdir");
+ goto l_delivery_init_tmpdir_fatal;
+ }
}
if (!ctx->storage.tmpdir) {
ctx->storage.tmpdir = strdup(globals.tmpdir);
+ if (!ctx->storage.tmpdir) {
+ SYSERROR("unable to allocate globals.tmpdir");
+ goto l_delivery_init_tmpdir_fatal;
+ }
}
- return unusable;
+ unusable = 0;
+ // TODO: Figure out why this breaks EVERYTHING
+ //if (need_setenv) {
+ // setenv("TMPDIR", ctx->storage.tmpdir, 1);
+ //}
l_delivery_init_tmpdir_fatal:
- unusable = 1;
+ guard_free(tmpdir);
return unusable;
}
@@ -94,7 +129,7 @@ void delivery_init_dirs_stage1(struct Delivery *ctx) {
char *rootdir = getenv("STASIS_ROOT");
if (rootdir) {
if (isempty(rootdir)) {
- fprintf(stderr, "STASIS_ROOT is set, but empty. Please assign a file system path to this environment variable.\n");
+ SYSERROR("STASIS_ROOT is set, but empty. Please assign a file system path to this environment variable.");
exit(1);
}
path_store(&ctx->storage.root, PATH_MAX, rootdir, ctx->info.build_name);
@@ -105,9 +140,10 @@ void delivery_init_dirs_stage1(struct Delivery *ctx) {
path_store(&ctx->storage.tools_dir, PATH_MAX, ctx->storage.root, "tools");
path_store(&ctx->storage.tmpdir, PATH_MAX, ctx->storage.root, "tmp");
if (delivery_init_tmpdir(ctx)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "Set $TMPDIR to a location other than %s\n", globals.tmpdir);
- if (globals.tmpdir)
+ SYSERROR("Set $TMPDIR to a location other than %s", globals.tmpdir);
+ if (globals.tmpdir) {
guard_free(globals.tmpdir);
+ }
exit(1);
}
@@ -119,7 +155,7 @@ void delivery_init_dirs_stage1(struct Delivery *ctx) {
}
if (access(ctx->storage.mission_dir, F_OK)) {
- msg(STASIS_MSG_L1, "%s: %s\n", ctx->storage.mission_dir, strerror(errno));
+ msg(STASIS_MSG_L1, "%s: %s: mission directory does not exist\n", ctx->storage.mission_dir, strerror(errno));
exit(1);
}
@@ -150,60 +186,77 @@ void delivery_init_dirs_stage1(struct Delivery *ctx) {
}
int delivery_init_platform(struct Delivery *ctx) {
- msg(STASIS_MSG_L2, "Setting architecture\n");
+ SYSDEBUG("Setting architecture");
char archsuffix[20];
struct utsname uts;
if (uname(&uts)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "uname() failed: %s\n", strerror(errno));
+ SYSERROR("uname() failed: %s", strerror(errno));
return -1;
}
ctx->system.platform = calloc(DELIVERY_PLATFORM_MAX + 1, sizeof(*ctx->system.platform));
if (!ctx->system.platform) {
- SYSERROR("Unable to allocate %d records for platform array\n", DELIVERY_PLATFORM_MAX);
+ SYSERROR("Unable to allocate %d records for platform array", DELIVERY_PLATFORM_MAX);
return -1;
}
for (size_t i = 0; i < DELIVERY_PLATFORM_MAX; i++) {
ctx->system.platform[i] = calloc(DELIVERY_PLATFORM_MAXLEN, sizeof(*ctx->system.platform[0]));
+ if (!ctx->system.platform[i]) {
+ SYSERROR("Unable to allocate record %zu in platform array", i);
+ guard_array_n_free(ctx->system.platform, i);
+ return -1;
+ }
}
ctx->system.arch = strdup(uts.machine);
if (!ctx->system.arch) {
// memory error
+ guard_array_n_free(ctx->system.platform, DELIVERY_PLATFORM_MAX);
+ ctx->system.platform = NULL;
return -1;
}
if (!strcmp(ctx->system.arch, "x86_64")) {
- strcpy(archsuffix, "64");
+ strncpy(archsuffix, "64", sizeof(archsuffix) - 1);
} else {
- strcpy(archsuffix, ctx->system.arch);
+ strncpy(archsuffix, ctx->system.arch, sizeof(archsuffix) - 1);
}
+ archsuffix[sizeof(archsuffix) - 1] = '\0';
+
+ SYSDEBUG("Setting platform");
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM], uts.sysname, DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
- msg(STASIS_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");
+ snprintf(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], DELIVERY_PLATFORM_MAXLEN, "osx-%s", archsuffix);
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "MacOSX", DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], "macos", DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_RELEASE][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
} 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");
+ snprintf(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], DELIVERY_PLATFORM_MAXLEN, "linux-%s", archsuffix);
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "Linux", DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], "linux", DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_RELEASE][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
} 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]);
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], ctx->system.platform[DELIVERY_PLATFORM], DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], ctx->system.platform[DELIVERY_PLATFORM], DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
+ strncpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], ctx->system.platform[DELIVERY_PLATFORM], DELIVERY_PLATFORM_MAXLEN - 1);
+ ctx->system.platform[DELIVERY_PLATFORM_RELEASE][DELIVERY_PLATFORM_MAXLEN - 1] = '\0';
tolower_s(ctx->system.platform[DELIVERY_PLATFORM_RELEASE]);
}
long cpu_count = get_cpu_count();
if (!cpu_count) {
- fprintf(stderr, "Unable to determine CPU count. Falling back to 1.\n");
+ SYSERROR("Unable to determine CPU count. Falling back to 1.");
cpu_count = 1;
}
char ncpus[100] = {0};
- sprintf(ncpus, "%ld", cpu_count);
+ snprintf(ncpus, sizeof(ncpus), "%ld", cpu_count);
// Declare some important bits as environment variables
setenv("CPU_COUNT", ncpus, 1);
@@ -246,22 +299,33 @@ int delivery_init(struct Delivery *ctx, int render_mode) {
}
// Configure architecture and platform information
- delivery_init_platform(ctx);
+ if (delivery_init_platform(ctx)) {
+ // memory error
+ return -1;
+ }
// Create STASIS directory structure
delivery_init_dirs_stage1(ctx);
char config_local[PATH_MAX];
- sprintf(config_local, "%s/%s", ctx->storage.tmpdir, "config");
+ snprintf(config_local, sizeof(config_local), "%s/%s", ctx->storage.tmpdir, "config");
setenv("XDG_CONFIG_HOME", config_local, 1);
+ if (mkdirs(config_local, 0755)) {
+ SYSERROR("%s: unable to create directory", config_local);
+ // fall through because XDG doesn't _really_ need to be there
+ }
char cache_local[PATH_MAX];
- sprintf(cache_local, "%s/%s", ctx->storage.tmpdir, "cache");
+ snprintf(cache_local, sizeof(cache_local), "%s/%s", ctx->storage.tmpdir, "cache");
setenv("XDG_CACHE_HOME", cache_local, 1);
+ if (mkdirs(cache_local, 0755)) {
+ SYSERROR("%s: unable to create directory", cache_local);
+ // fall through because XDG doesn't _really_ need to be there
+ }
// add tools to PATH
char pathvar_tmp[STASIS_BUFSIZ];
- sprintf(pathvar_tmp, "%s/bin:%s", ctx->storage.tools_dir, getenv("PATH"));
+ snprintf(pathvar_tmp, sizeof(pathvar_tmp), "%s/bin:%s", ctx->storage.tools_dir, getenv("PATH"));
setenv("PATH", pathvar_tmp, 1);
// Prevent git from paginating output
@@ -287,18 +351,31 @@ int delivery_init(struct Delivery *ctx, int render_mode) {
int bootstrap_build_info(struct Delivery *ctx) {
struct Delivery local = {0};
+ memcpy(&local.deploy.docker.capabilities, &ctx->deploy.docker.capabilities, sizeof(local.deploy.docker.capabilities));
+
+ SYSDEBUG("ini_open(%s)", ctx->_stasis_ini_fp.cfg_path);
local._stasis_ini_fp.cfg = ini_open(ctx->_stasis_ini_fp.cfg_path);
+ SYSDEBUG("ini_open(%s)", ctx->_stasis_ini_fp.delivery_path);
local._stasis_ini_fp.delivery = ini_open(ctx->_stasis_ini_fp.delivery_path);
+
if (delivery_init_platform(&local)) {
+ SYSDEBUG("delivery_init_platform failed");
+ delivery_free(&local);
return -1;
}
if (populate_delivery_cfg(&local, INI_READ_RENDER)) {
+ SYSDEBUG("populate_delivery_cfg failed");
+ delivery_free(&local);
return -1;
}
if (populate_delivery_ini(&local, INI_READ_RENDER)) {
+ SYSDEBUG("populate_delivery_ini failed");
+ delivery_free(&local);
return -1;
}
if (populate_info(&local)) {
+ SYSDEBUG("populate_info failed");
+ delivery_free(&local);
return -1;
}
ctx->info.build_name = strdup(local.info.build_name);
@@ -308,12 +385,14 @@ int bootstrap_build_info(struct Delivery *ctx) {
ctx->info.time_info = malloc(sizeof(*ctx->info.time_info));
if (!ctx->info.time_info) {
SYSERROR("Unable to allocate %zu bytes for tm struct: %s", sizeof(*local.info.time_info), strerror(errno));
+ delivery_free(&local);
return -1;
}
}
memcpy(ctx->info.time_info, local.info.time_info, sizeof(*local.info.time_info));
ctx->info.time_now = local.info.time_now;
ctx->info.time_str_epoch = strdup(local.info.time_str_epoch);
+ SYSDEBUG("delivery_free local resources");
delivery_free(&local);
return 0;
}
@@ -321,11 +400,11 @@ int bootstrap_build_info(struct Delivery *ctx) {
int delivery_exists(struct Delivery *ctx) {
int release_exists = DELIVERY_NOT_FOUND;
char release_pattern[PATH_MAX] = {0};
- sprintf(release_pattern, "*%s*", ctx->info.release_name);
+ snprintf(release_pattern, sizeof(release_pattern), "*%s*", ctx->info.release_name);
if (globals.enable_artifactory) {
if (jfrt_auth_init(&ctx->deploy.jfrog_auth)) {
- fprintf(stderr, "Failed to initialize Artifactory authentication context\n");
+ SYSERROR("Failed to initialize Artifactory authentication context");
return -1; // error
}
diff --git a/src/lib/delivery/delivery_install.c b/src/lib/delivery/delivery_install.c
index 246c604..efdb819 100644
--- a/src/lib/delivery/delivery_install.c
+++ b/src/lib/delivery/delivery_install.c
@@ -1,8 +1,11 @@
#include "delivery.h"
+#include "conda.h"
+#include "wheelinfo.h"
+#include "version_compare.h"
static struct Test *requirement_from_test(struct Delivery *ctx, const char *name) {
struct Test *result = NULL;
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
+ for (size_t i = 0; i < ctx->tests->num_used; i++) {
char *package_name = strdup(name);
if (package_name) {
char *spec = find_version_spec(package_name);
@@ -11,8 +14,8 @@ static struct Test *requirement_from_test(struct Delivery *ctx, const char *name
}
remove_extras(package_name);
- if (ctx->tests[i].name && !strcmp(package_name, ctx->tests[i].name)) {
- result = &ctx->tests[i];
+ if (ctx->tests->test[i]->name && !strcmp(package_name, ctx->tests->test[i]->name)) {
+ result = ctx->tests->test[i];
guard_free(package_name);
break;
}
@@ -32,11 +35,13 @@ static char *have_spec_in_config(const struct Delivery *ctx, const char *name) {
char package[255] = {0};
if (op) {
strncpy(package, config_spec, op - config_spec);
+ package[op - config_spec] = '\0';
} else {
strncpy(package, config_spec, sizeof(package) - 1);
+ package[sizeof(package) - 1] = '\0';
}
remove_extras(package);
- if (strncmp(package, name, strlen(package)) == 0) {
+ if (strncmp(package, name, strlen(name)) == 0) {
return config_spec;
}
}
@@ -81,8 +86,10 @@ int delivery_overlay_packages_from_env(struct Delivery *ctx, const char *env_nam
char *op = find_version_spec(spec);
if (op) {
strncpy(spec_name, spec, op - spec);
+ spec_name[op - spec] = '\0';
} else {
strncpy(spec_name, spec, sizeof(spec_name) - 1);
+ spec_name[sizeof(spec_name) - 1] = '\0';
}
struct Test *test_block = requirement_from_test(ctx, spec_name);
@@ -102,8 +109,10 @@ int delivery_overlay_packages_from_env(struct Delivery *ctx, const char *env_nam
// we only care about packages with specs here. if something else arrives, ignore it
if (op) {
strncpy(frozen_name, frozen_spec, op - frozen_spec);
+ frozen_name[op - frozen_spec] = '\0';
} else {
strncpy(frozen_name, frozen_spec, sizeof(frozen_name) - 1);
+ frozen_name[sizeof(frozen_name) - 1] = '\0';
}
struct Test *test = requirement_from_test(ctx, frozen_name);
if (test && strcmp(test->name, frozen_name) == 0) {
@@ -128,6 +137,159 @@ int delivery_overlay_packages_from_env(struct Delivery *ctx, const char *env_nam
return 0;
}
+int delivery_conda_enforce_package_version(struct Delivery *ctx, const char *env_name, const char *name) {
+ char *spec_installed = NULL;
+ char *spec_request = NULL;
+ int status = 0;
+
+ if (isempty((char *) env_name)) {
+ SYSERROR("environment name cannot be NULL or empty");
+ return -1;
+ }
+ if (isempty((char *) name)) {
+ SYSERROR("name cannot be NULL or empty");
+ return -1;
+ }
+
+ int proc_status = 0;
+ char cmd[PATH_MAX] = {0};
+ snprintf(cmd, PATH_MAX, "conda list --name %s", env_name);
+
+ char *output = shell_output(cmd, &proc_status);
+ if (!output || proc_status) {
+ SYSERROR("unable to retreive list of installed packages (exit: %d)", proc_status);
+ guard_free(output);
+ return -1;
+ }
+
+ struct StrList *lines = strlist_init();
+ if (!lines) {
+ SYSERROR("unable to allocate memory for installed package list");
+ guard_free(output);
+ status = -1;
+ goto cleanup;
+ }
+
+ if (strlist_append_tokenize(lines, output, LINE_SEP)) {
+ SYSERROR("unable to tokenize installed package list");
+ guard_free(output);
+ strlist_free(&lines);
+ status = -1;
+ goto cleanup;
+ }
+
+ for (size_t i = 0; i < strlist_count(lines); i++) {
+ char *line = strlist_item(lines, i);
+ if (!line) {
+ SYSERROR("line is NULL");
+ status = -1;
+ goto cleanup;
+ }
+ if (startswith(line, "#") || isempty(line)) {
+ continue;
+ }
+ collapse_whitespace(&line);
+ strip(line);
+
+ struct StrList *tokens = strlist_init();
+ if (!tokens) {
+ SYSERROR("unable to allocate memory for tokenized installed package list");
+ status = -1;
+ goto cleanup;
+ }
+
+ if (strlist_append_tokenize(tokens, line, " ")) {
+ SYSERROR("unable to tokenize installed package list");
+ status = -1;
+ goto cleanup;
+ }
+
+ const char *installed_version = strlist_item(tokens, 1);
+ if (!installed_version) {
+ SYSERROR("not enough data in line (name and version not found)");
+ guard_strlist_free(&tokens);
+ status = -1;
+ goto cleanup;
+ }
+
+ if (strstr(line, name)) {
+ spec_installed = strdup(installed_version);
+ if (!spec_installed) {
+ SYSERROR("unable to allocated memory for installed package version");
+ guard_strlist_free(&tokens);
+ status = -1;
+ goto cleanup;
+ }
+ guard_strlist_free(&tokens);
+ break;
+ }
+
+ guard_strlist_free(&tokens);
+ }
+
+ for (size_t i = 0; i < strlist_count(ctx->conda.conda_packages); i++) {
+ const char *item = strlist_item(ctx->conda.conda_packages, i);
+ if (!item) {
+ SYSERROR("conda_packages list record %zu is NULL", i);
+ status = -1;
+ goto cleanup;
+ }
+ if (strstr(item, name)) {
+ const char *spec_tmp = find_version_spec((char *) item);
+ if (spec_tmp) {
+ while (!isalnum(*spec_tmp)) {
+ spec_tmp++;
+ }
+ spec_request = strdup(spec_tmp);
+ } else {
+ spec_request = strdup(item);
+ }
+
+ if (!spec_request) {
+ SYSERROR("unable to allocate memory for conda package spec request");
+ status = -1;
+ goto cleanup;
+ }
+ break;
+ }
+ }
+
+ const int stop = version_compare(NOT | EQ, spec_request, spec_installed);
+ if (stop < 0) {
+ SYSERROR("version comparison failed (spec_request: %s, spec_installed: %s)", spec_request, spec_installed);
+ status = -1;
+ goto cleanup;
+ }
+ if (stop == 0) {
+ goto cleanup;
+ }
+
+ snprintf(cmd, PATH_MAX, "remove --name %s %s", env_name, name);
+ if (conda_exec(cmd)) {
+ SYSERROR("unable to remove package %s from %s", name, env_name);
+ status = -1;
+ goto cleanup;
+ }
+ snprintf(cmd, PATH_MAX, "install --name %s %s=%s", env_name, name, spec_request);
+ if (conda_exec(cmd)) {
+ SYSERROR("unable to install package %s into %s", name, env_name);
+ status = -1;
+ goto cleanup;
+ }
+
+ cleanup:
+ guard_free(spec_request);
+ guard_free(spec_installed);
+ strlist_free(&lines);
+ guard_free(output);
+ return status;
+}
+
+static int fn_nop(const char *command) {
+ (void) command;
+ return 1;
+}
+
int delivery_purge_packages(struct Delivery *ctx, const char *env_name, int use_pkg_manager) {
int status = 0;
char subcommand[100] = {0};
@@ -145,19 +307,24 @@ int delivery_purge_packages(struct Delivery *ctx, const char *env_name, int use_
case PKG_USE_CONDA:
fn = conda_exec;
list = ctx->conda.conda_packages_purge;
- strcpy(package_manager, "conda");
+ strncpy(package_manager, "conda", sizeof(package_manager) - 1);
+ package_manager[sizeof(package_manager) - 1] = '\0';
// conda is already configured for "always_yes"
- strcpy(subcommand, "remove");
+ strncpy(subcommand, "remove", sizeof(subcommand) - 1);
+ subcommand[sizeof(subcommand) - 1] = '\0';
break;
case PKG_USE_PIP:
fn = pip_exec;
list = ctx->conda.pip_packages_purge;
- strcpy(package_manager, "pip");
+ strncpy(package_manager, "pip", sizeof(package_manager) - 1);
+ package_manager[sizeof(package_manager) - 1] = '\0';
// avoid user prompt to remove packages
- strcpy(subcommand, "uninstall -y");
+ strncpy(subcommand, "uninstall -y", sizeof(subcommand) - 1);
+ subcommand[sizeof(subcommand) - 1] = '\0';
break;
default:
SYSERROR("Unknown package manager: %d", use_pkg_manager);
+ fn = fn_nop;
status = -1;
break;
}
@@ -166,7 +333,7 @@ int delivery_purge_packages(struct Delivery *ctx, const char *env_name, int use_
char *package = strlist_item(list, i);
char *command = NULL;
if (asprintf(&command, "%s '%s'", subcommand, package) < 0) {
- SYSERROR("%s", "Unable to allocate bytes for removal command");
+ SYSERROR("Unable to allocate bytes for removal command");
status = -1;
break;
}
@@ -175,11 +342,12 @@ int delivery_purge_packages(struct Delivery *ctx, const char *env_name, int use_
SYSERROR("%s removal operation failed", package_manager);
guard_free(command);
status = 1;
- break;
+ goto cleanup;
}
guard_free(command);
}
+ cleanup:
if (current_env) {
conda_activate(ctx->storage.conda_install_prefix, current_env);
guard_free(current_env);
@@ -189,8 +357,7 @@ int delivery_purge_packages(struct Delivery *ctx, const char *env_name, int use_
}
int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, char *env_name, int type, struct StrList **manifest) {
- char cmd[PATH_MAX];
- char pkgs[STASIS_BUFSIZ];
+ char command_base[PATH_MAX];
const char *env_current = getenv("CONDA_DEFAULT_ENV");
if (env_current) {
@@ -203,9 +370,8 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha
}
}
- memset(cmd, 0, sizeof(cmd));
- memset(pkgs, 0, sizeof(pkgs));
- strcat(cmd, "install");
+ memset(command_base, 0, sizeof(command_base));
+ strncat(command_base, "install", sizeof(command_base) - strlen(command_base) - 1);
typedef int (*Runner)(const char *);
Runner runner = NULL;
@@ -215,18 +381,31 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha
runner = pip_exec;
}
+ if (!runner) {
+ SYSERROR("Invalid callback runner of type: %d", type);
+ return -1;
+ }
+
if (INSTALL_PKG_CONDA_DEFERRED & type) {
- strcat(cmd, " --use-local");
+ strncat(command_base, " --use-local", sizeof(command_base) - strlen(command_base) - 1);
+ command_base[sizeof(command_base) - 1] = '\0';
} else if (INSTALL_PKG_PIP_DEFERRED & type) {
// Don't change the baseline package set unless we're working with a
// new build. Release candidates will need to keep packages as stable
// as possible between releases.
if (!ctx->meta.based_on) {
- strcat(cmd, " --upgrade");
+ strncat(command_base, " --upgrade", sizeof(command_base) - strlen(command_base) - 1);
+ command_base[sizeof(command_base) - 1] = '\0';
}
+ snprintf(command_base + strlen(command_base), sizeof(command_base) - strlen(command_base), " --extra-index-url 'file://%s'", ctx->storage.wheel_artifact_dir);
}
- sprintf(cmd + strlen(cmd), " --extra-index-url 'file://%s'", ctx->storage.wheel_artifact_dir);
+ size_t args_alloc_len = STASIS_BUFSIZ;
+ char *args = calloc(args_alloc_len + 1, sizeof(*args));
+ if (!args) {
+ SYSERROR("Unable to allocate bytes for command arguments");
+ return -1;
+ }
for (size_t x = 0; manifest[x] != NULL; x++) {
char *name = NULL;
@@ -237,79 +416,131 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha
continue;
}
if (INSTALL_PKG_PIP_DEFERRED & type) {
+ SYSDEBUG("Getting requirements for test: %s", name);
struct Test *info = requirement_from_test(ctx, name);
if (info) {
- if (!strcmp(info->version, "HEAD")) {
+ if (!strcmp(info->version, "HEAD") || is_git_sha(info->version)) {
+ SYSDEBUG("Using version: %s", info->version);
struct StrList *tag_data = strlist_init();
if (!tag_data) {
- SYSERROR("%s", "Unable to allocate memory for tag data\n");
+ SYSERROR("Unable to allocate memory for tag data");
+ guard_free(args);
return -1;
}
+ SYSDEBUG("Tokenizing repository info tag: %s", info->repository_info_tag);
strlist_append_tokenize(tag_data, info->repository_info_tag, "-");
- struct Wheel *whl = NULL;
+ struct WheelInfo *whl = NULL;
char *post_commit = NULL;
char *hash = NULL;
if (strlist_count(tag_data) > 1) {
post_commit = strlist_item(tag_data, 1);
+ SYSDEBUG("post_commit: %s", post_commit);
hash = strlist_item(tag_data, 2);
+ SYSDEBUG("hash: %s", hash);
}
// We can't match on version here (index 0). The wheel's version is not guaranteed to be
// equal to the tag; setuptools_scm auto-increments the value, the user can change it manually,
// etc.
errno = 0;
- whl = get_wheel_info(ctx->storage.wheel_artifact_dir, info->name,
+ SYSDEBUG("%s", "Getting wheel information");
+ whl = wheelinfo_get(ctx->storage.wheel_artifact_dir, info->name,
(char *[]) {ctx->meta.python_compact, ctx->system.arch,
"none", "any",
post_commit, hash,
NULL}, WHEEL_MATCH_ANY);
if (!whl && errno) {
// error
- SYSERROR("Unable to read Python wheel info: %s\n", strerror(errno));
+ SYSERROR("Unable to read Python wheel info: %s", strerror(errno));
exit(1);
} else if (!whl) {
// not found
- fprintf(stderr, "No wheel packages found that match the description of '%s'", info->name);
+ SYSERROR("No wheel packages found that match the description of '%s'", info->name);
} else {
- // found
- guard_strlist_free(&tag_data);
+ // found, replace the original version with newly detected version
+ SYSDEBUG("Replacing version: %s", whl->version);
+ guard_free(info->version);
info->version = strdup(whl->version);
+ SYSDEBUG("Version replaced with: %s", whl->version);
}
- wheel_free(&whl);
+ guard_strlist_free(&tag_data);
+ wheelinfo_free(&whl);
}
char req[255] = {0};
if (!strcmp(name, info->name)) {
- strcpy(req, info->name);
+ strncpy(req, info->name, sizeof(req) - 1);
+ req[sizeof(req) - 1] = '\0';
} else {
- strcpy(req, name);
+ strncpy(req, name, sizeof(req) - 1);
+ req[sizeof(req) - 1] = '\0';
char *spec = find_version_spec(req);
if (spec) {
*spec = 0;
}
}
- snprintf(cmd + strlen(cmd),
- sizeof(cmd) - strlen(cmd) - strlen(info->name) - strlen(info->version) + 5,
- " '%s==%s'", req, info->version);
+ const char *fmt_append = "%s '%s==%s'";
+ const char *fmt = " '%s==%s'";
+ const int required_len = snprintf(NULL, 0, fmt_append, args, req, info->version);
+ if (required_len > (int) args_alloc_len) {
+ if (grow(required_len, &args_alloc_len, &args)) {
+ SYSERROR("Unable to allocate %d bytes for command arguments", required_len);
+ guard_free(args);
+ return -1;
+ }
+ }
+ snprintf(args + strlen(args), args_alloc_len - strlen(args), fmt, req, info->version);
} else {
- fprintf(stderr, "Deferred package '%s' is not present in the tested package list!\n", name);
+ SYSERROR("Deferred package '%s' is not present in the tested package list!", name);
+ guard_free(args);
return -1;
}
} else {
if (startswith(name, "--") || startswith(name, "-")) {
- sprintf(cmd + strlen(cmd), " %s", name);
+ const char *fmt_append = "%s %s";
+ const char *fmt = " %s";
+ const int required_len = snprintf(NULL, 0, fmt_append, args, name);
+ if (required_len > (int) args_alloc_len) {
+ if (grow(required_len, &args_alloc_len, &args)) {
+ SYSERROR("Unable to allocate %d bytes for command arguments", required_len);
+ guard_free(args);
+ return -1;
+ }
+ }
+ snprintf(args + strlen(args), args_alloc_len - strlen(args), fmt, name);
} else {
- sprintf(cmd + strlen(cmd), " '%s'", name);
+ const char *fmt_append = "%s '%s'";
+ const char *fmt = " '%s'";
+ const int required_len = snprintf(NULL, 0, fmt_append, args, name);
+ if (required_len > (int) args_alloc_len) {
+ if (grow(required_len, &args_alloc_len, &args)) {
+ SYSERROR("Unable to allocate %d bytes for command arguments", required_len);
+ guard_free(args);
+ return -1;
+ }
+ }
+ snprintf(args + strlen(args), args_alloc_len - strlen(args), fmt, name);
}
}
}
- int status = runner(cmd);
+ char *command = NULL;
+ if (asprintf(&command, "%s %s", command_base, args) < 0) {
+ SYSERROR("Unable to allocate bytes for command");
+ guard_free(args);
+ return -1;
+ }
+
+ int status = runner(command);
+ guard_free(args);
+ guard_free(command);
if (status) {
+ // fail quickly
return status;
}
}
+ guard_free(args);
return 0;
}
diff --git a/src/lib/delivery/delivery_populate.c b/src/lib/delivery/delivery_populate.c
index 84676f1..cfa3da2 100644
--- a/src/lib/delivery/delivery_populate.c
+++ b/src/lib/delivery/delivery_populate.c
@@ -33,18 +33,18 @@ int populate_info(struct Delivery *ctx) {
if (!ctx->info.time_info) {
ctx->info.time_info = malloc(sizeof(*ctx->info.time_info));
if (!ctx->info.time_info) {
- msg(STASIS_MSG_ERROR, "%s: Unable to allocate memory for time_info\n", strerror(errno));
+ SYSERROR("%s: Unable to allocate memory for time_info", strerror(errno));
return -1;
}
if (!localtime_r(&ctx->info.time_now, ctx->info.time_info)) {
- msg(STASIS_MSG_ERROR, "%s: localtime_r failed\n", strerror(errno));
+ SYSERROR("%s: localtime_r failed", strerror(errno));
return -1;
}
}
ctx->info.time_str_epoch = calloc(STASIS_TIME_STR_MAX, sizeof(*ctx->info.time_str_epoch));
if (!ctx->info.time_str_epoch) {
- msg(STASIS_MSG_ERROR, "%s: Unable to allocate memory for Unix epoch string\n", strerror(errno));
+ SYSERROR("%s: Unable to allocate memory for Unix epoch string", strerror(errno));
return -1;
}
snprintf(ctx->info.time_str_epoch, STASIS_TIME_STR_MAX - 1, "%li", ctx->info.time_now);
@@ -55,6 +55,7 @@ int populate_info(struct Delivery *ctx) {
int populate_delivery_cfg(struct Delivery *ctx, int render_mode) {
struct INIFILE *cfg = ctx->_stasis_ini_fp.cfg;
if (!cfg) {
+ SYSDEBUG("cfg is NULL");
return -1;
}
int err = 0;
@@ -84,6 +85,45 @@ int populate_delivery_cfg(struct Delivery *ctx, int render_mode) {
}
globals.pip_packages = ini_getval_strlist(cfg, "default", "pip_packages", LINE_SEP, render_mode, &err);
+ err = 0;
+ if (!globals.wheel_builder) {
+ globals.wheel_builder = ini_getval_str(cfg, "default", "wheel_builder", render_mode, &err);
+ if (err) {
+ SYSWARN("wheel_builder is undefined. Falling back to system toolchain: 'build'.");
+ globals.wheel_builder = strdup("build");
+ if (!globals.wheel_builder) {
+ SYSERROR("unable to allocate memory for default wheel_builder value");
+ return -1;
+ }
+ }
+ }
+
+ err = 0;
+ if (!globals.wheel_builder_manylinux_image) {
+ globals.wheel_builder_manylinux_image = ini_getval_str(cfg, "default", "wheel_builder_manylinux_image", render_mode, &err);
+ }
+
+ if (err && globals.wheel_builder && strcmp(globals.wheel_builder, "manylinux") == 0) {
+ SYSERROR("default:wheel_builder is set to 'manylinux', however default:wheel_builder_manylinux_image is not configured");
+ return -1;
+ }
+
+ if (strcmp(globals.wheel_builder, "manylinux") == 0) {
+ char *manifest_inspect_cmd = NULL;
+ if (asprintf(&manifest_inspect_cmd, "manifest inspect '%s'", globals.wheel_builder_manylinux_image) < 0) {
+ SYSERROR("unable to allocate memory for docker command");
+ guard_free(manifest_inspect_cmd);
+ return -1;
+ }
+ if (ctx->deploy.docker.capabilities.usable && docker_exec(manifest_inspect_cmd, STASIS_DOCKER_QUIET_STDOUT)) {
+ SYSERROR("Image provided by default:wheel_builder_manylinux_image does not exist: %s", globals.wheel_builder_manylinux_image);
+ guard_free(manifest_inspect_cmd);
+ return -1;
+ }
+ guard_free(manifest_inspect_cmd);
+ }
+
+
if (globals.jfrog.jfrog_artifactory_base_url) {
guard_free(globals.jfrog.jfrog_artifactory_base_url);
}
@@ -153,6 +193,7 @@ static void normalize_ini_list(struct INIFILE **inip, struct StrList **listp, ch
(*inip) = ini;
(*listp) = list;
}
+
int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
struct INIFILE *ini = ctx->_stasis_ini_fp.delivery;
struct INIData *rtdata;
@@ -162,8 +203,6 @@ int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
// keys in the configuration
RuntimeEnv *rt = runtime_copy(__environ);
while ((rtdata = ini_getall(ini, "runtime")) != NULL) {
- char rec[STASIS_BUFSIZ];
- sprintf(rec, "%s=%s", lstrip(strip(rtdata->key)), lstrip(strip(rtdata->value)));
runtime_set(rt, rtdata->key, rtdata->value);
}
runtime_apply(rt);
@@ -201,7 +240,9 @@ int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
normalize_ini_list(&ini, &ctx->conda.pip_packages_purge, "conda", "pip_packages_purge", render_mode);
// Delivery metadata consumed
- populate_mission_ini(&ctx, render_mode);
+ if (populate_mission_ini(&ctx, render_mode)) {
+ return -1;
+ }
if (ctx->info.release_name) {
guard_free(ctx->info.release_name);
@@ -215,16 +256,16 @@ int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
return -1;
}
- if (delivery_format_str(ctx, &ctx->info.release_name, ctx->rules.release_fmt)) {
- fprintf(stderr, "Failed to generate release name. Format used: %s\n", ctx->rules.release_fmt);
+ if (delivery_format_str(ctx, &ctx->info.release_name, STASIS_NAME_MAX, ctx->rules.release_fmt)) {
+ SYSERROR("Failed to generate release name. Format used: %s", ctx->rules.release_fmt);
return -1;
}
if (!ctx->info.build_name) {
- delivery_format_str(ctx, &ctx->info.build_name, ctx->rules.build_name_fmt);
+ delivery_format_str(ctx, &ctx->info.build_name, STASIS_NAME_MAX, ctx->rules.build_name_fmt);
}
if (!ctx->info.build_number) {
- delivery_format_str(ctx, &ctx->info.build_number, ctx->rules.build_number_fmt);
+ delivery_format_str(ctx, &ctx->info.build_number, STASIS_NAME_MAX, ctx->rules.build_number_fmt);
}
// Best I can do to make output directories unique. Annoying.
@@ -237,11 +278,17 @@ int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
ctx->conda.pip_packages_defer = strlist_init();
}
- for (size_t z = 0, i = 0; i < ini->section_count; i++) {
+ ctx->tests = tests_init(TEST_NUM_ALLOC_INITIAL);
+ for (size_t i = 0; i < ini->section_count; i++) {
char *section_name = ini->section[i]->key;
if (startswith(section_name, "test:")) {
union INIVal val;
- struct Test *test = &ctx->tests[z];
+ struct Test *test = test_init();
+ if (!test) {
+ SYSERROR("unable to allocate memory for test structure");
+ return -1;
+ }
+
val.as_char_p = strchr(ini->section[i]->key, ':') + 1;
if (val.as_char_p && isempty(val.as_char_p)) {
return 1;
@@ -259,8 +306,21 @@ int populate_delivery_ini(struct Delivery *ctx, int render_mode) {
}
test->repository_remove_tags = ini_getval_strlist(ini, section_name, "repository_remove_tags", LINE_SEP, render_mode, &err);
test->build_recipe = ini_getval_str(ini, section_name, "build_recipe", render_mode, &err);
- test->runtime.environ = ini_getval_strlist(ini, section_name, "runtime", LINE_SEP, render_mode, &err);
- z++;
+
+ test->runtime->environ = ini_getval_strlist(ini, section_name, "runtime", LINE_SEP, render_mode, &err);
+ const char *timeout_str = ini_getval_str(ini, section_name, "timeout", render_mode, &err);
+ if (timeout_str) {
+ test->timeout = str_to_timeout((char *) timeout_str);
+ if (test->timeout == STR_TO_TIMEOUT_INVALID_TIME_SCALE) {
+ SYSERROR("In 'test:%s', invalid time scale format: %s. Use n[hms].", test->name, timeout_str);
+ return 1;
+ }
+ if (test->timeout == STR_TO_TIMEOUT_NEGATIVE) {
+ SYSERROR("In 'test:%s', timeout cannot be negative: %s", test->name, timeout_str);
+ return 1;
+ }
+ }
+ tests_add(ctx->tests, test);
}
}
@@ -309,25 +369,26 @@ int populate_mission_ini(struct Delivery **ctx, int render_mode) {
int err = 0;
if ((*ctx)->_stasis_ini_fp.mission) {
+ // mission configurations are optional
return 0;
}
// Now populate the rules
char missionfile[PATH_MAX] = {0};
if (getenv("STASIS_SYSCONFDIR")) {
- sprintf(missionfile, "%s/%s/%s/%s.ini",
+ snprintf(missionfile, sizeof(missionfile), "%s/%s/%s/%s.ini",
getenv("STASIS_SYSCONFDIR"), "mission", (*ctx)->meta.mission, (*ctx)->meta.mission);
} else {
- sprintf(missionfile, "%s/%s/%s/%s.ini",
+ snprintf(missionfile, sizeof(missionfile), "%s/%s/%s/%s.ini",
globals.sysconfdir, "mission", (*ctx)->meta.mission, (*ctx)->meta.mission);
}
- msg(STASIS_MSG_L2, "Reading mission configuration: %s\n", missionfile);
+ SYSDEBUG("Reading mission configuration: %s", missionfile);
(*ctx)->_stasis_ini_fp.mission = ini_open(missionfile);
struct INIFILE *ini = (*ctx)->_stasis_ini_fp.mission;
if (!ini) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Failed to read mission configuration: %s, %s\n", missionfile, strerror(errno));
- exit(1);
+ SYSERROR("Failed to read mission configuration: %s, %s", missionfile, strerror(errno));
+ return -1;
}
(*ctx)->_stasis_ini_fp.mission_path = strdup(missionfile);
@@ -343,7 +404,7 @@ int populate_mission_ini(struct Delivery **ctx, int render_mode) {
void validate_delivery_ini(struct INIFILE *ini) {
if (!ini) {
- SYSERROR("%s", "INIFILE is NULL!");
+ SYSERROR("INIFILE is NULL!");
exit(1);
}
if (ini_section_search(&ini, INI_SEARCH_EXACT, "meta")) {
@@ -353,7 +414,7 @@ void validate_delivery_ini(struct INIFILE *ini) {
ini_has_key_required(ini, "meta", "mission");
ini_has_key_required(ini, "meta", "python");
} else {
- SYSERROR("%s", "[meta] configuration section is required");
+ SYSERROR("[meta] configuration section is required");
exit(1);
}
@@ -363,7 +424,7 @@ void validate_delivery_ini(struct INIFILE *ini) {
ini_has_key_required(ini, "conda", "installer_platform");
ini_has_key_required(ini, "conda", "installer_arch");
} else {
- SYSERROR("%s", "[conda] configuration section is required");
+ SYSERROR("[conda] configuration section is required");
exit(1);
}
diff --git a/src/lib/delivery/delivery_postprocess.c b/src/lib/delivery/delivery_postprocess.c
index 5029e02..63093b3 100644
--- a/src/lib/delivery/delivery_postprocess.c
+++ b/src/lib/delivery/delivery_postprocess.c
@@ -1,8 +1,12 @@
#include "delivery.h"
+#include "log.h"
+#include "conda.h"
const char *release_header = "# delivery_name: %s\n"
"# delivery_fmt: %s\n"
+ "# stasis_version: %s\n"
+ "# stasis_branch: %s\n"
"# creation_time: %s\n"
"# conda_ident: %s\n"
"# conda_build_ident: %s\n";
@@ -11,9 +15,11 @@ char *delivery_get_release_header(struct Delivery *ctx) {
char output[STASIS_BUFSIZ];
char stamp[100];
strftime(stamp, sizeof(stamp) - 1, "%c", ctx->info.time_info);
- sprintf(output, release_header,
+ snprintf(output, sizeof(output), release_header,
ctx->info.release_name,
ctx->rules.release_fmt,
+ STASIS_VERSION,
+ STASIS_VERSION_BRANCH,
stamp,
ctx->conda.tool_version,
ctx->conda.tool_build_version);
@@ -22,14 +28,16 @@ char *delivery_get_release_header(struct Delivery *ctx) {
int delivery_dump_metadata(struct Delivery *ctx) {
char filename[PATH_MAX];
- sprintf(filename, "%s/meta-%s.stasis", ctx->storage.meta_dir, ctx->info.release_name);
+ snprintf(filename, sizeof(filename), "%s/meta-%s.stasis", ctx->storage.meta_dir, ctx->info.release_name);
FILE *fp = fopen(filename, "w+");
if (!fp) {
return -1;
}
if (globals.verbose) {
- printf("%s\n", filename);
+ msg(STASIS_MSG_L2, "%s\n", filename);
}
+ fprintf(fp, "stasis_version %s\n", STASIS_VERSION);
+ fprintf(fp, "stasis_version_branch %s\n", STASIS_VERSION_BRANCH);
fprintf(fp, "name %s\n", ctx->meta.name);
fprintf(fp, "version %s\n", ctx->meta.version);
fprintf(fp, "rc %d\n", ctx->meta.rc);
@@ -66,16 +74,16 @@ void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage)
FILE *tp = NULL;
if (stage == DELIVERY_REWRITE_SPEC_STAGE_1) {
- SYSDEBUG("%s", "Entering stage 1");
+ SYSDEBUG("Entering stage 1");
header = delivery_get_release_header(ctx);
SYSDEBUG("Release header:\n%s", header);
if (!header) {
- msg(STASIS_MSG_ERROR, "failed to generate release header string\n", filename);
+ SYSERROR("failed to generate release header string", filename);
exit(1);
}
tempfile = xmkstemp(&tp, "w+");
if (!tempfile || !tp) {
- msg(STASIS_MSG_ERROR, "%s: unable to create temporary file\n", strerror(errno));
+ SYSERROR("%s: unable to create temporary file", strerror(errno));
exit(1);
}
SYSDEBUG("Writing header to temporary file: %s", tempfile);
@@ -84,7 +92,7 @@ void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage)
// Read the original file
char **contents = file_readlines(filename, 0, 0, NULL);
if (!contents) {
- msg(STASIS_MSG_ERROR, "%s: unable to read %s", filename);
+ SYSERROR("%s: unable to read %s", filename);
exit(1);
}
@@ -128,45 +136,45 @@ void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage)
// Replace the original file with our temporary data
if (copy2(tempfile, filename, CT_PERM) < 0) {
- fprintf(stderr, "%s: could not rename '%s' to '%s'\n", strerror(errno), tempfile, filename);
+ SYSERROR("%s: could not rename '%s' to '%s'", strerror(errno), tempfile, filename);
exit(1);
}
SYSDEBUG("Removing file: %s", tempfile);
remove(tempfile);
guard_free(tempfile);
} else if (globals.enable_rewrite_spec_stage_2 && stage == DELIVERY_REWRITE_SPEC_STAGE_2) {
- SYSDEBUG("%s", "Entering stage 2");
+ SYSDEBUG("Entering stage 2");
char output[PATH_MAX] = {0};
// Replace "local" channel with the staging URL
if (ctx->storage.conda_staging_url) {
- SYSDEBUG("%s", "Will replace conda channel with staging area url");
+ SYSDEBUG("Will replace conda channel with staging area url");
file_replace_text(filename, "@CONDA_CHANNEL@", ctx->storage.conda_staging_url, 0);
} else if (globals.jfrog.repo) {
- SYSDEBUG("%s", "Will replace conda channel with artifactory repo packages/conda url");
- sprintf(output, "%s/%s/%s/%s/packages/conda", globals.jfrog.url, globals.jfrog.repo, ctx->meta.mission, ctx->info.build_name);
+ SYSDEBUG("Will replace conda channel with artifactory repo packages/conda url");
+ snprintf(output, sizeof(output), "%s/%s/%s/%s/packages/conda", globals.jfrog.url, globals.jfrog.repo, ctx->meta.mission, ctx->info.build_name);
file_replace_text(filename, "@CONDA_CHANNEL@", output, 0);
} else {
- SYSDEBUG("%s", "Will replace conda channel with local conda artifact directory");
- msg(STASIS_MSG_WARN, "conda_staging_dir is not configured. Using fallback: '%s'\n", ctx->storage.conda_artifact_dir);
+ SYSDEBUG("Will replace conda channel with local conda artifact directory");
+ SYSWARN("conda_staging_dir is not configured. Using fallback: '%s'", ctx->storage.conda_artifact_dir);
file_replace_text(filename, "@CONDA_CHANNEL@", ctx->storage.conda_artifact_dir, 0);
}
if (ctx->storage.wheel_staging_url) {
- SYSDEBUG("%s", "Will replace pip arguments with wheel staging url");
- sprintf(output, "--extra-index-url %s/%s/%s/packages/wheels", ctx->storage.wheel_staging_url, ctx->meta.mission, ctx->info.build_name);
+ SYSDEBUG("Will replace pip arguments with wheel staging url");
+ snprintf(output, sizeof(output), "--extra-index-url %s/%s/%s/packages/wheels", ctx->storage.wheel_staging_url, ctx->meta.mission, ctx->info.build_name);
file_replace_text(filename, "@PIP_ARGUMENTS@", ctx->storage.wheel_staging_url, 0);
} else if (globals.enable_artifactory && globals.jfrog.url && globals.jfrog.repo) {
- SYSDEBUG("%s", "Will replace pip arguments with artifactory repo packages/wheel url");
- sprintf(output, "--extra-index-url %s/%s/%s/%s/packages/wheels", globals.jfrog.url, globals.jfrog.repo, ctx->meta.mission, ctx->info.build_name);
+ SYSDEBUG("Will replace pip arguments with artifactory repo packages/wheel url");
+ snprintf(output, sizeof(output), "--extra-index-url %s/%s/%s/%s/packages/wheels", globals.jfrog.url, globals.jfrog.repo, ctx->meta.mission, ctx->info.build_name);
file_replace_text(filename, "@PIP_ARGUMENTS@", output, 0);
} else {
- SYSDEBUG("%s", "Will replace pip arguments with local wheel artifact directory");
- msg(STASIS_MSG_WARN, "wheel_staging_dir is not configured. Using fallback: '%s'\n", ctx->storage.wheel_artifact_dir);
- sprintf(output, "--extra-index-url file://%s", ctx->storage.wheel_artifact_dir);
+ SYSDEBUG("Will replace pip arguments with local wheel artifact directory");
+ SYSWARN("wheel_staging_dir is not configured. Using fallback: '%s'", ctx->storage.wheel_artifact_dir);
+ snprintf(output, sizeof(output), "--extra-index-url file://%s", ctx->storage.wheel_artifact_dir);
file_replace_text(filename, "@PIP_ARGUMENTS@", output, 0);
}
}
- SYSDEBUG("%s", "Rewriting finished");
+ SYSDEBUG("Rewriting finished");
}
int delivery_copy_conda_artifacts(struct Delivery *ctx) {
@@ -177,16 +185,15 @@ int delivery_copy_conda_artifacts(struct Delivery *ctx) {
memset(conda_build_dir, 0, sizeof(conda_build_dir));
memset(subdir, 0, sizeof(subdir));
- sprintf(conda_build_dir, "%s/%s", ctx->storage.conda_install_prefix, "conda-bld");
+ snprintf(conda_build_dir, sizeof(conda_build_dir), "%s/%s", ctx->storage.conda_install_prefix, "conda-bld");
// One must run conda build at least once to create the "conda-bld" directory.
// When this directory is missing there can be no build artifacts.
if (access(conda_build_dir, F_OK) < 0) {
- msg(STASIS_MSG_RESTRICT | STASIS_MSG_WARN | STASIS_MSG_L3,
- "Skipped: 'conda build' has never been executed.\n");
+ SYSWARN("Skipped: 'conda build' has never been executed.");
return 0;
}
- snprintf(cmd, sizeof(cmd) - 1, "rsync -avi --progress %s/%s %s",
+ snprintf(cmd, sizeof(cmd), "rsync -avi --progress %s/%s %s",
conda_build_dir,
ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR],
ctx->storage.conda_artifact_dir);
@@ -200,7 +207,7 @@ int delivery_index_conda_artifacts(struct Delivery *ctx) {
int delivery_copy_wheel_artifacts(struct Delivery *ctx) {
char cmd[PATH_MAX] = {0};
- snprintf(cmd, sizeof(cmd) - 1, "rsync -avi --progress %s/*/dist/*.whl %s",
+ snprintf(cmd, sizeof(cmd), "rsync -avi --progress %s/*/dist/*.whl %s",
ctx->storage.build_sources_dir,
ctx->storage.wheel_artifact_dir);
return system(cmd);
@@ -217,7 +224,7 @@ int delivery_index_wheel_artifacts(struct Delivery *ctx) {
// Generate a "dumb" local pypi index that is compatible with:
// pip install --extra-index-url
char top_index[PATH_MAX] = {0};
- sprintf(top_index, "%s/index.html", ctx->storage.wheel_artifact_dir);
+ snprintf(top_index, sizeof(top_index),"%s/index.html", ctx->storage.wheel_artifact_dir);
SYSDEBUG("Opening top-level index for writing: %s", top_index);
FILE *top_fp = fopen(top_index, "w+");
if (!top_fp) {
@@ -232,11 +239,12 @@ int delivery_index_wheel_artifacts(struct Delivery *ctx) {
}
char bottom_index[PATH_MAX * 2] = {0};
- sprintf(bottom_index, "%s/%s/index.html", ctx->storage.wheel_artifact_dir, rec->d_name);
+ snprintf(bottom_index, sizeof(bottom_index), "%s/%s/index.html", ctx->storage.wheel_artifact_dir, rec->d_name);
SYSDEBUG("Opening bottom-level for writing: %s", bottom_index);
FILE *bottom_fp = fopen(bottom_index, "w+");
if (!bottom_fp) {
closedir(dp);
+ fclose(top_fp);
return -3;
}
@@ -248,7 +256,7 @@ int delivery_index_wheel_artifacts(struct Delivery *ctx) {
fprintf(top_fp, "<a href=\"%s/\">%s</a><br/>\n", rec->d_name, rec->d_name);
char dpath[PATH_MAX * 2] = {0};
- sprintf(dpath, "%s/%s", ctx->storage.wheel_artifact_dir, rec->d_name);
+ snprintf(dpath, sizeof(dpath), "%s/%s", ctx->storage.wheel_artifact_dir, rec->d_name);
struct StrList *packages = listdir(dpath);
if (!packages) {
closedir(dp);
@@ -275,6 +283,6 @@ int delivery_index_wheel_artifacts(struct Delivery *ctx) {
}
closedir(dp);
fclose(top_fp);
- SYSDEBUG("%s", "Wheel indexing complete");
+ SYSDEBUG("Wheel indexing complete");
return 0;
}
diff --git a/src/lib/delivery/delivery_show.c b/src/lib/delivery/delivery_show.c
index adfa1be..1740688 100644
--- a/src/lib/delivery/delivery_show.c
+++ b/src/lib/delivery/delivery_show.c
@@ -84,13 +84,13 @@ void delivery_conda_show(struct Delivery *ctx) {
void delivery_tests_show(struct Delivery *ctx) {
printf("\n====TESTS====\n");
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
- if (!ctx->tests[i].name) {
+ for (size_t i = 0; i < ctx->tests->num_used; i++) {
+ if (!ctx->tests->test[i]->name) {
continue;
}
- printf("%-20s %-20s %s\n", ctx->tests[i].name,
- ctx->tests[i].version,
- ctx->tests[i].repository);
+ printf("%-20s %-20s %s\n", ctx->tests->test[i]->name,
+ ctx->tests->test[i]->version,
+ ctx->tests->test[i]->repository);
}
}
@@ -108,7 +108,7 @@ void delivery_runtime_show(struct Delivery *ctx) {
char *item = strlist_item(rt, i);
if (!item) {
// not supposed to occur
- msg(STASIS_MSG_WARN | STASIS_MSG_L1, "Encountered unexpected NULL at record %zu of %zu of runtime array.\n", i);
+ SYSWARN("Encountered unexpected NULL in record %zu of %zu in runtime array.", i, total);
return;
}
printf("%s\n", item);
diff --git a/src/lib/delivery/delivery_test.c b/src/lib/delivery/delivery_test.c
index e80e0ec..f59a62e 100644
--- a/src/lib/delivery/delivery_test.c
+++ b/src/lib/delivery/delivery_test.c
@@ -1,5 +1,87 @@
#include "delivery.h"
+struct Tests *tests_init(const size_t num_tests) {
+ struct Tests *tests = calloc(1, sizeof(*tests));
+ if (!tests) {
+ return NULL;
+ }
+
+ tests->test = calloc(num_tests, sizeof(*tests->test));
+ if (!tests->test) {
+ guard_free(tests);
+ return NULL;
+ }
+ tests->num_used = 0;
+ tests->num_alloc = num_tests;
+
+ return tests;
+}
+
+int tests_add(struct Tests *tests, struct Test *x) {
+ if (tests->num_used >= tests->num_alloc) {
+ const size_t old_alloc = tests->num_alloc;
+ struct Test **tmp = realloc(tests->test, tests->num_alloc++ * sizeof(**tests->test));
+ SYSDEBUG("Increasing size of test array: %zu -> %zu", old_alloc, tests->num_alloc);
+ if (!tmp) {
+ SYSDEBUG("Failed to allocate %zu bytes for test array", tests->num_alloc * sizeof(**tests->test));
+ return -1;
+ }
+ tests->test = tmp;
+ }
+
+ SYSDEBUG("Adding test: '%s'", x->name);
+ tests->test[tests->num_used++] = x;
+ return 0;
+}
+
+struct Test *test_init() {
+ struct Test *result = calloc(1, sizeof(*result));
+ if (!result) {
+ return NULL;
+ }
+
+ result->runtime = calloc(1, sizeof(*result->runtime));
+ if (!result->runtime) {
+ guard_free(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+void test_free(struct Test **x) {
+ struct Test *test = *x;
+ if (!test) {
+ return;
+ }
+ guard_free(test->name);
+ guard_free(test->version);
+ guard_free(test->repository);
+ guard_free(test->repository_info_ref);
+ guard_free(test->repository_info_tag);
+ guard_strlist_free(&test->repository_remove_tags);
+ guard_free(test->script);
+ guard_free(test->script_setup);
+ guard_free(test->build_recipe);
+ // test-specific runtime variables
+ guard_runtime_free(test->runtime->environ);
+ guard_free(test->runtime);
+ guard_free(test);
+}
+
+void tests_free(struct Tests **x) {
+ struct Tests *tests = *x;
+ if (!tests) {
+ return;
+ }
+
+ for (size_t i = 0; i < tests->num_alloc; i++) {
+ test_free(&tests->test[i]);
+ }
+ guard_free(tests->test);
+ guard_free(tests);
+}
+
void delivery_tests_run(struct Delivery *ctx) {
static const int SETUP = 0;
static const int PARALLEL = 1;
@@ -16,26 +98,26 @@ void delivery_tests_run(struct Delivery *ctx) {
// amount of debug information.
snprintf(globals.workaround.conda_reactivate, PATH_MAX - 1, "\nset +x; mamba activate ${CONDA_DEFAULT_ENV}; set -x\n");
- if (!ctx->tests[0].name) {
- msg(STASIS_MSG_WARN | STASIS_MSG_L2, "no tests are defined!\n");
+ if (!ctx->tests || !ctx->tests->num_used) {
+ SYSWARN("no tests are defined!");
} else {
pool[PARALLEL] = mp_pool_init("parallel", ctx->storage.tmpdir);
if (!pool[PARALLEL]) {
- perror("mp_pool_init/parallel");
+ SYSERROR("mp_pool_init/parallel initialization failed");
exit(1);
}
pool[PARALLEL]->status_interval = globals.pool_status_interval;
pool[SERIAL] = mp_pool_init("serial", ctx->storage.tmpdir);
if (!pool[SERIAL]) {
- perror("mp_pool_init/serial");
+ SYSERROR("mp_pool_init/serial initialization failed");
exit(1);
}
pool[SERIAL]->status_interval = globals.pool_status_interval;
pool[SETUP] = mp_pool_init("setup", ctx->storage.tmpdir);
if (!pool[SETUP]) {
- perror("mp_pool_init/setup");
+ SYSERROR("mp_pool_init/setup initialization failed");
exit(1);
}
pool[SETUP]->status_interval = globals.pool_status_interval;
@@ -60,21 +142,20 @@ void delivery_tests_run(struct Delivery *ctx) {
// Iterate over our test records, retrieving the source code for each package, and assigning its scripted tasks
// to the appropriate processing pool
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
- struct Test *test = &ctx->tests[i];
+ for (size_t i = 0; i < ctx->tests->num_used; i++) {
+ struct Test *test = ctx->tests->test[i];
if (!test->name && !test->repository && !test->script) {
// skip unused test records
continue;
}
msg(STASIS_MSG_L2, "Loading tests for %s %s\n", test->name, test->version);
if (!test->script || !strlen(test->script)) {
- msg(STASIS_MSG_WARN | STASIS_MSG_L3, "Nothing to do. To fix, declare a 'script' in section: [test:%s]\n",
- test->name);
+ SYSWARN("Nothing to do. To fix, declare a 'script' in section: [test:%s]", test->name);
continue;
}
char destdir[PATH_MAX];
- sprintf(destdir, "%s/%s", ctx->storage.build_sources_dir, path_basename(test->repository));
+ snprintf(destdir, sizeof(destdir), "%s/%s", ctx->storage.build_sources_dir, path_basename(test->repository));
if (!access(destdir, F_OK)) {
msg(STASIS_MSG_L3, "Purging repository %s\n", destdir);
@@ -97,6 +178,18 @@ void delivery_tests_run(struct Delivery *ctx) {
if (pushd(destdir)) {
COE_CHECK_ABORT(1, "Unable to enter repository directory\n");
} else {
+ const int dep_status = check_python_package_dependencies(".");
+ if (dep_status) {
+ SYSERROR("Please replace all occurrences above with standard package specs:\n"
+ "\n"
+ " package==x.y.z\n"
+ " package>=x.y.z\n"
+ " package<=x.y.z\n"
+ " ...\n"
+ "\n");
+ COE_CHECK_ABORT(true, "Unreproducible delivery");
+ }
+
char *cmd = calloc(strlen(test->script) + STASIS_BUFSIZ, sizeof(*cmd));
if (!cmd) {
SYSERROR("Unable to allocate test script buffer: %s", strerror(errno));
@@ -106,11 +199,12 @@ void delivery_tests_run(struct Delivery *ctx) {
msg(STASIS_MSG_L3, "Queuing task for %s\n", test->name);
memset(&proc, 0, sizeof(proc));
- strcpy(cmd, test->script);
+ strncpy(cmd, test->script, strlen(test->script) + STASIS_BUFSIZ - 1);
+ cmd[strlen(test->script) + STASIS_BUFSIZ - 1] = '\0';
char *cmd_rendered = tpl_render(cmd);
if (cmd_rendered) {
if (strcmp(cmd_rendered, cmd) != 0) {
- strcpy(cmd, cmd_rendered);
+ strncpy(cmd, cmd_rendered, strlen(test->script) + STASIS_BUFSIZ - 1);
cmd[strlen(cmd_rendered) ? strlen(cmd_rendered) - 1 : 0] = 0;
}
guard_free(cmd_rendered);
@@ -135,7 +229,8 @@ void delivery_tests_run(struct Delivery *ctx) {
if (!globals.enable_parallel || !test->parallel) {
selected = SERIAL;
memset(pool_name, 0, sizeof(pool_name));
- strcpy(pool_name, "serial");
+ strncpy(pool_name, "serial", sizeof(pool_name) - 1);
+ pool_name[sizeof(pool_name) - 1] = '\0';
}
if (asprintf(&runner_cmd, runner_cmd_fmt, cmd) < 0) {
@@ -154,6 +249,12 @@ void delivery_tests_run(struct Delivery *ctx) {
}
exit(1);
}
+
+ // Apply timeout from test block
+ if (test->timeout) {
+ task->timeout = test->timeout;
+ }
+
guard_free(runner_cmd);
guard_free(cmd);
popd();
@@ -163,11 +264,11 @@ void delivery_tests_run(struct Delivery *ctx) {
// Configure "script_setup" tasks
// Directories should exist now, so no need to go through initializing everything all over again.
- for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) {
- struct Test *test = &ctx->tests[i];
+ for (size_t i = 0; i < ctx->tests->num_used; i++) {
+ const struct Test *test = ctx->tests->test[i];
if (test->script_setup) {
char destdir[PATH_MAX];
- sprintf(destdir, "%s/%s", ctx->storage.build_sources_dir, path_basename(test->repository));
+ snprintf(destdir, sizeof(destdir), "%s/%s", ctx->storage.build_sources_dir, path_basename(test->repository));
if (access(destdir, F_OK)) {
SYSERROR("%s: %s", destdir, strerror(errno));
exit(1);
@@ -181,11 +282,13 @@ void delivery_tests_run(struct Delivery *ctx) {
}
strncpy(cmd, test->script_setup, cmd_len - 1);
+ cmd[cmd_len - 1] = '\0';
+
char *cmd_rendered = tpl_render(cmd);
if (cmd_rendered) {
if (strcmp(cmd_rendered, cmd) != 0) {
strncpy(cmd, cmd_rendered, cmd_len - 1);
- cmd[strlen(cmd_rendered) ? strlen(cmd_rendered) - 1 : 0] = 0;
+ cmd[strlen(cmd_rendered) ? strlen(cmd_rendered) - 1 : 0] = '\0';
}
guard_free(cmd_rendered);
} else {
@@ -217,7 +320,7 @@ void delivery_tests_run(struct Delivery *ctx) {
guard_free(cmd);
popd();
} else {
- SYSERROR("Failed to change directory: %s\n", destdir);
+ SYSERROR("Failed to change directory: %s", destdir);
exit(1);
}
}
@@ -282,10 +385,10 @@ int delivery_fixup_test_results(struct Delivery *ctx) {
continue;
}
- sprintf(path, "%s/%s", ctx->storage.results_dir, rec->d_name);
+ snprintf(path, sizeof(path), "%s/%s", ctx->storage.results_dir, rec->d_name);
msg(STASIS_MSG_L3, "%s\n", rec->d_name);
if (xml_pretty_print_in_place(path, STASIS_XML_PRETTY_PRINT_PROG, STASIS_XML_PRETTY_PRINT_ARGS)) {
- msg(STASIS_MSG_L3 | STASIS_MSG_WARN, "Failed to rewrite file '%s'\n", rec->d_name);
+ SYSWARN("Failed to rewrite file '%s'", rec->d_name);
}
}
diff --git a/src/lib/delivery/include/delivery.h b/src/lib/delivery/include/delivery.h
index 26a5499..3103a86 100644
--- a/src/lib/delivery/include/delivery.h
+++ b/src/lib/delivery/include/delivery.h
@@ -5,20 +5,11 @@
#include <string.h>
#include <stdbool.h>
-#include <unistd.h>
-#include <sys/utsname.h>
-#include <fnmatch.h>
-#include <sys/statvfs.h>
#include "artifactory.h"
-#include "conda.h"
-#include "copy.h"
-#include "core.h"
#include "docker.h"
#include "environment.h"
#include "ini.h"
#include "multiprocessing.h"
-#include "recipe.h"
-#include "wheel.h"
#define DELIVERY_PLATFORM_MAX 4
#define DELIVERY_PLATFORM_MAXLEN 65
@@ -44,6 +35,28 @@ struct Content {
char *data;
};
+//! Number of test records to allocate (grows dynamically)
+#define TEST_NUM_ALLOC_INITIAL 10
+
+/*! \struct Test
+ * \brief Test information
+ */
+struct Test {
+ char *name; ///< Name of package
+ char *version; ///< Version of package
+ char *repository; ///< Git repository of package
+ char *script_setup; ///< Commands to execute before the main script
+ char *script; ///< Commands to execute
+ bool disable; ///< Toggle a test block
+ bool parallel; ///< Toggle parallel or serial execution
+ char *build_recipe; ///< Conda recipe to build (optional)
+ char *repository_info_ref; ///< Git commit hash
+ char *repository_info_tag; ///< Git tag (first parent)
+ struct StrList *repository_remove_tags; ///< Git tags to remove (to fix duplicate commit tags)
+ struct Runtime *runtime; ///< Environment variables specific to the test context
+ int timeout; ///< Timeout in seconds
+}; ///< An array of tests
+
/*! \struct Delivery
* \brief A structure describing a full delivery object
*/
@@ -64,10 +77,8 @@ struct Delivery {
* \brief System information
*/
struct System {
- char *arch;
- ///< System CPU architecture ident
- char **platform;
- ///< System platform name
+ char *arch; ///< System CPU architecture ident
+ char **platform; ///< System platform name
} system;
/*! \struct Storage
* \brief Storage paths
@@ -155,23 +166,11 @@ struct Delivery {
RuntimeEnv *environ; ///< Environment variables
} runtime;
- /*! \struct Test
- * \brief Test information
- */
- struct Test {
- char *name; ///< Name of package
- char *version; ///< Version of package
- char *repository; ///< Git repository of package
- char *script_setup; ///< Commands to execute before the main script
- char *script; ///< Commands to execute
- bool disable; ///< Toggle a test block
- bool parallel; ///< Toggle parallel or serial execution
- char *build_recipe; ///< Conda recipe to build (optional)
- char *repository_info_ref; ///< Git commit hash
- char *repository_info_tag; ///< Git tag (first parent)
- struct StrList *repository_remove_tags; ///< Git tags to remove (to fix duplicate commit tags)
- struct Runtime runtime; ///< Environment variables specific to the test context
- } tests[1000]; ///< An array of tests
+ struct Tests {
+ struct Test **test;
+ size_t num_used;
+ size_t num_alloc;
+ } *tests;
struct Deploy {
struct JFRT_Auth jfrog_auth;
@@ -311,9 +310,10 @@ int delivery_get_conda_installer(struct Delivery *ctx, char *installer_url);
* Generate URL based on Delivery context
* @param ctx pointer to Delivery context
* @param result pointer to char
+ * @param maxlen
* @return in result
*/
-void delivery_get_conda_installer_url(struct Delivery *ctx, char *result);
+void delivery_get_conda_installer_url(struct Delivery *ctx, char *result, size_t maxlen);
/**
* Install packages based on Delivery context
@@ -384,10 +384,11 @@ void delivery_install_conda(char *install_script, char *conda_install_dir);
*
* @param ctx pointer to Delivery context
* @param dest NULL pointer to string, or initialized string
+ * @param maxlen
* @param fmt release format string
* @return 0 on success, -1 on error
*/
-int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt);
+int delivery_format_str(struct Delivery *ctx, char **dest, size_t maxlen, const char *fmt);
// helper function
int delivery_gather_tool_versions(struct Delivery *ctx);
@@ -439,6 +440,16 @@ int delivery_exists(struct Delivery *ctx);
int delivery_overlay_packages_from_env(struct Delivery *ctx, const char *env_name);
/**
+ * Conda does not handle version suffixes well, if at all. For example, if pkg-1.2.3rc1 is installed Conda will
+ * silently ignore a request to install pkg-1.2.3. This function serves as a workaround by comparing the version
+ * on-disk, and the requested version from the package list, and if the versions are not equal the on-disk package
+ * is replaced by the one in the package list.
+ *
+ * When a package is present in the list without a pinned version it will be reinstalled with whatever is available
+ */
+int delivery_conda_enforce_package_version(struct Delivery *ctx, const char *env_name, const char *name);
+
+/**
* Retrieve remote deliveries associated with the current version series
* @param ctx Delivery context
* @return -1 on error
@@ -459,4 +470,69 @@ int delivery_series_sync(struct Delivery *ctx);
*/
int delivery_purge_packages(struct Delivery *ctx, const char *env_name, int use_pkg_manager);
+/**
+ * Export delivery environments
+ *
+ * @param ctx Delivery context
+ * @param envs array of conda environment names
+ */
+void delivery_export(const struct Delivery *ctx, char *envs[]);
+
+/**
+ * STAGE 1: Rewrite delivery-related strings in specfile
+ *
+ * @param ctx Delivery context
+ * @param specfile path to YAML spec file
+ */
+void delivery_rewrite_stage1(struct Delivery *ctx, char *specfile);
+
+/**
+ * STAGE 2: Rewrite delivery-related strings in specfile
+ *
+ * @param ctx Delivery context
+ * @param specfile path to YAML spec file
+ */
+void delivery_rewrite_stage2(struct Delivery *ctx, char *specfile);
+
+/**
+ * Return a copy of a delivery context
+ * @param ctx Delivery context
+ * @return a copy
+ */
+struct Delivery *delivery_duplicate(struct Delivery *ctx);
+
+/**
+ * Initialize a `Tests` structure
+ * @param num_tests number of test records
+ * @return a an initialized `Tests` structure
+ */
+struct Tests *tests_init(size_t num_tests);
+
+/**
+ * Add a `Test` structure to `Tests`
+ * @param tests list to add to
+ * @param x test to add to list
+ * @return 0=success, -1=error
+ */
+int tests_add(struct Tests *tests, struct Test *x);
+
+/**
+ * Free a `Test` structure
+ * @param x pointer to `Test`
+ */
+void test_free(struct Test **x);
+
+/**
+ * Free a `Tests` structure
+ * @param x pointer to `Tests`
+ */
+void tests_free(struct Tests **x);
+
+/**
+ * Initialize a `Test` structure
+ * @return an initialized `Test` structure
+ */
+struct Test *test_init();
+
+
#endif //STASIS_DELIVERY_H