aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2025-01-07 11:05:30 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2025-01-07 11:14:44 -0500
commit0d637cc928eca740d6823337a10e0344a4f259db (patch)
treeb02e44966f863a7bb01707b1137cda1c0d9aab9b /src
parentfacbd1473fd1914d7c2e702a3de2227bd7b4bfe1 (diff)
downloadstasis-0d637cc928eca740d6823337a10e0344a4f259db.tar.gz
Implement --no-export
Diffstat (limited to 'src')
-rw-r--r--src/cli/stasis/include/args.h1
-rw-r--r--src/cli/stasis/stasis_main.c384
-rw-r--r--src/lib/core/include/core.h1
3 files changed, 196 insertions, 190 deletions
diff --git a/src/cli/stasis/include/args.h b/src/cli/stasis/include/args.h
index 5bad752..89cb328 100644
--- a/src/cli/stasis/include/args.h
+++ b/src/cli/stasis/include/args.h
@@ -17,6 +17,7 @@
#define OPT_FAIL_FAST 1009
#define OPT_NO_PARALLEL 1010
#define OPT_POOL_STATUS_INTERVAL 1011
+#define OPT_NO_EXPORT 1012
extern struct option long_options[];
void usage(char *progname);
diff --git a/src/cli/stasis/stasis_main.c b/src/cli/stasis/stasis_main.c
index 36dfecc..8a98efd 100644
--- a/src/cli/stasis/stasis_main.c
+++ b/src/cli/stasis/stasis_main.c
@@ -12,12 +12,7 @@
int main(int argc, char *argv[]) {
- struct Delivery ctx;
- struct Process proc = {
- .f_stdout = "",
- .f_stderr = "",
- .redirect_stderr = 0,
- };
+ struct Delivery ctx = {0};
char env_name[STASIS_NAME_MAX] = {0};
char env_name_testing[STASIS_NAME_MAX] = {0};
char *delivery_input = NULL;
@@ -109,6 +104,9 @@ int main(int argc, char *argv[]) {
case OPT_NO_TESTING:
globals.enable_testing = false;
break;
+ case OPT_NO_EXPORT:
+ globals.enable_export = false;
+ break;
case OPT_NO_REWRITE_SPEC_STAGE_2:
globals.enable_rewrite_spec_stage_2 = false;
break;
@@ -273,154 +271,158 @@ int main(int argc, char *argv[]) {
exit(1);
}
- msg(STASIS_MSG_L1, "Conda setup\n");
- delivery_get_conda_installer_url(&ctx, installer_url);
- msg(STASIS_MSG_L2, "Downloading: %s\n", installer_url);
- if (delivery_get_conda_installer(&ctx, installer_url)) {
- msg(STASIS_MSG_ERROR, "download failed: %s\n", installer_url);
- exit(1);
- }
-
- msg(STASIS_MSG_L2, "Installing: %s\n", ctx.conda.installer_name);
- delivery_install_conda(ctx.conda.installer_path, ctx.storage.conda_install_prefix);
-
- msg(STASIS_MSG_L2, "Configuring: %s\n", ctx.storage.conda_install_prefix);
- delivery_conda_enable(&ctx, ctx.storage.conda_install_prefix);
-
- //
- // Implied environment creation modes/actions
- //
- // 1. No base environment config
- // 1a. Caller is warned
- // 1b. Caller has full control over all packages
- // 2. Default base environment (etc/stasis/mission/[name]/base.yml)
- // 2a. Depends on packages defined by base.yml
- // 2b. Caller may issue a reduced package set in the INI config
- // 2c. Caller must be vigilant to avoid incompatible packages (base.yml
- // *should* have no version constraints)
- // 3. External base environment (based_on=schema://[release_name].yml)
- // 3a. Depends on a previous release or arbitrary yaml configuration
- // 3b. Bugs, conflicts, and dependency resolution issues are inherited and
- // must be handled in the INI config
- msg(STASIS_MSG_L1, "Creating release environment(s)\n");
-
- char *mission_base = NULL;
- if (isempty(ctx.meta.based_on)) {
- guard_free(ctx.meta.based_on);
- char *mission_base_orig = NULL;
-
- if (asprintf(&mission_base_orig, "%s/%s/base.yml", ctx.storage.mission_dir, ctx.meta.mission) < 0) {
- SYSERROR("Unable to allocate bytes for %s/%s/base.yml path\n", ctx.storage.mission_dir, ctx.meta.mission);
+ if (globals.enable_export) {
+ msg(STASIS_MSG_L1, "Conda setup\n");
+ delivery_get_conda_installer_url(&ctx, installer_url);
+ msg(STASIS_MSG_L2, "Downloading: %s\n", installer_url);
+ if (delivery_get_conda_installer(&ctx, installer_url)) {
+ msg(STASIS_MSG_ERROR, "download failed: %s\n", installer_url);
exit(1);
}
- if (access(mission_base_orig, F_OK) < 0) {
- msg(STASIS_MSG_L2 | STASIS_MSG_WARN, "Mission does not provide a base.yml configuration: %s (%s)\n",
- ctx.meta.mission, ctx.storage.mission_dir);
- } else {
- msg(STASIS_MSG_L2, "Using base environment configuration: %s\n", mission_base_orig);
- if (asprintf(&mission_base, "%s/%s-base.yml", ctx.storage.tmpdir, ctx.info.release_name) < 0) {
- SYSERROR("%s", "Unable to allocate bytes for temporary base.yml configuration");
- remove(mission_base);
+ msg(STASIS_MSG_L2, "Installing: %s\n", ctx.conda.installer_name);
+ delivery_install_conda(ctx.conda.installer_path, ctx.storage.conda_install_prefix);
+
+ msg(STASIS_MSG_L2, "Configuring: %s\n", ctx.storage.conda_install_prefix);
+ delivery_conda_enable(&ctx, ctx.storage.conda_install_prefix);
+
+ //
+ // Implied environment creation modes/actions
+ //
+ // 1. No base environment config
+ // 1a. Caller is warned
+ // 1b. Caller has full control over all packages
+ // 2. Default base environment (etc/stasis/mission/[name]/base.yml)
+ // 2a. Depends on packages defined by base.yml
+ // 2b. Caller may issue a reduced package set in the INI config
+ // 2c. Caller must be vigilant to avoid incompatible packages (base.yml
+ // *should* have no version constraints)
+ // 3. External base environment (based_on=schema://[release_name].yml)
+ // 3a. Depends on a previous release or arbitrary yaml configuration
+ // 3b. Bugs, conflicts, and dependency resolution issues are inherited and
+ // must be handled in the INI config
+ msg(STASIS_MSG_L1, "Creating release environment(s)\n");
+
+ char *mission_base = NULL;
+ if (isempty(ctx.meta.based_on)) {
+ guard_free(ctx.meta.based_on);
+ char *mission_base_orig = NULL;
+
+ if (asprintf(&mission_base_orig, "%s/%s/base.yml", ctx.storage.mission_dir, ctx.meta.mission) < 0) {
+ SYSERROR("Unable to allocate bytes for %s/%s/base.yml path\n", ctx.storage.mission_dir, ctx.meta.mission);
exit(1);
}
- copy2(mission_base_orig, mission_base, CT_OWNER | CT_PERM);
- char spec[255] = {0};
- snprintf(spec, sizeof(spec) - 1, "- python=%s\n", ctx.meta.python);
- file_replace_text(mission_base, "- python\n", spec, 0);
- ctx.meta.based_on = mission_base;
- }
- guard_free(mission_base_orig);
- }
- if (!isempty(ctx.meta.based_on)) {
- if (conda_env_exists(ctx.storage.conda_install_prefix, env_name) && conda_env_remove(env_name)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to remove release environment: %s\n", env_name);
- exit(1);
+ if (access(mission_base_orig, F_OK) < 0) {
+ msg(STASIS_MSG_L2 | STASIS_MSG_WARN, "Mission does not provide a base.yml configuration: %s (%s)\n",
+ ctx.meta.mission, ctx.storage.mission_dir);
+ } else {
+ msg(STASIS_MSG_L2, "Using base environment configuration: %s\n", mission_base_orig);
+ if (asprintf(&mission_base, "%s/%s-base.yml", ctx.storage.tmpdir, ctx.info.release_name) < 0) {
+ SYSERROR("%s", "Unable to allocate bytes for temporary base.yml configuration");
+ remove(mission_base);
+ exit(1);
+ }
+ copy2(mission_base_orig, mission_base, CT_OWNER | CT_PERM);
+ char spec[255] = {0};
+ snprintf(spec, sizeof(spec) - 1, "- python=%s\n", ctx.meta.python);
+ file_replace_text(mission_base, "- python\n", spec, 0);
+ ctx.meta.based_on = mission_base;
+ }
+ guard_free(mission_base_orig);
}
- msg(STASIS_MSG_L2, "Based on: %s\n", ctx.meta.based_on);
- if (conda_env_create_from_uri(env_name, ctx.meta.based_on)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to install release environment using configuration file\n");
- exit(1);
- }
+ if (!isempty(ctx.meta.based_on)) {
+ if (conda_env_exists(ctx.storage.conda_install_prefix, env_name) && conda_env_remove(env_name)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to remove release environment: %s\n", env_name);
+ exit(1);
+ }
- if (conda_env_exists(ctx.storage.conda_install_prefix, env_name_testing) && conda_env_remove(env_name_testing)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to remove testing environment %s\n", env_name_testing);
- exit(1);
- }
- if (conda_env_create_from_uri(env_name_testing, ctx.meta.based_on)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to install testing environment using configuration file\n");
- exit(1);
- }
- } else {
- if (conda_env_create(env_name, ctx.meta.python, NULL)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to create release environment\n");
- exit(1);
- }
- if (conda_env_create(env_name_testing, ctx.meta.python, NULL)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to create testing environment\n");
- exit(1);
- }
- }
- // The base environment configuration not used past this point
- remove(mission_base);
-
- const char *envs[] = {env_name_testing, env_name};
- for (size_t e = 0; e < sizeof(envs) / sizeof(*envs); e++) {
- const char *name = envs[e];
- if (ctx.conda.conda_packages_purge && strlist_count(ctx.conda.conda_packages_purge)) {
- msg(STASIS_MSG_L2, "Purging conda packages from %s\n", name);
- if (delivery_purge_packages(&ctx, name, PKG_USE_CONDA)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to purge requested conda packages from %s\n", name);
+ msg(STASIS_MSG_L2, "Based on: %s\n", ctx.meta.based_on);
+ if (conda_env_create_from_uri(env_name, ctx.meta.based_on)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to install release environment using configuration file\n");
exit(1);
}
- }
- if (ctx.conda.pip_packages_purge && strlist_count(ctx.conda.pip_packages_purge)) {
- msg(STASIS_MSG_L2, "Purging pip packages from %s\n", name);
- if (delivery_purge_packages(&ctx, env_name_testing, PKG_USE_PIP)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to purge requested pip packages from %s\n",
- env_name_testing);
+ if (conda_env_exists(ctx.storage.conda_install_prefix, env_name_testing) && conda_env_remove(env_name_testing)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to remove testing environment %s\n", env_name_testing);
+ exit(1);
+ }
+ if (conda_env_create_from_uri(env_name_testing, ctx.meta.based_on)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to install testing environment using configuration file\n");
+ exit(1);
+ }
+ } else {
+ if (conda_env_create(env_name, ctx.meta.python, NULL)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to create release environment\n");
+ exit(1);
+ }
+ if (conda_env_create(env_name_testing, ctx.meta.python, NULL)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to create testing environment\n");
exit(1);
}
}
- }
-
- // Activate test environment
- msg(STASIS_MSG_L1, "Activating test environment\n");
- if (conda_activate(ctx.storage.conda_install_prefix, env_name_testing)) {
- fprintf(stderr, "failed to activate test environment\n");
- exit(1);
- }
+ // The base environment configuration not used past this point
+ remove(mission_base);
+
+ const char *envs[] = {env_name_testing, env_name};
+ for (size_t e = 0; e < sizeof(envs) / sizeof(*envs); e++) {
+ const char *name = envs[e];
+ if (ctx.conda.conda_packages_purge && strlist_count(ctx.conda.conda_packages_purge)) {
+ msg(STASIS_MSG_L2, "Purging conda packages from %s\n", name);
+ if (delivery_purge_packages(&ctx, name, PKG_USE_CONDA)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to purge requested conda packages from %s\n", name);
+ exit(1);
+ }
+ }
- if (delivery_gather_tool_versions(&ctx)) {
- if (!ctx.conda.tool_version) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda version\n");
- exit(1);
+ if (ctx.conda.pip_packages_purge && strlist_count(ctx.conda.pip_packages_purge)) {
+ msg(STASIS_MSG_L2, "Purging pip packages from %s\n", name);
+ if (delivery_purge_packages(&ctx, env_name_testing, PKG_USE_PIP)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "unable to purge requested pip packages from %s\n",
+ env_name_testing);
+ exit(1);
+ }
+ }
}
- if (!ctx.conda.tool_build_version) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda-build version\n");
+
+ // Activate test environment
+ msg(STASIS_MSG_L1, "Activating test environment\n");
+ if (conda_activate(ctx.storage.conda_install_prefix, env_name_testing)) {
+ fprintf(stderr, "failed to activate test environment\n");
exit(1);
}
- }
- if (pip_exec("install build")) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "'build' tool installation failed\n");
- exit(1);
- }
+ if (delivery_gather_tool_versions(&ctx)) {
+ if (!ctx.conda.tool_version) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda version\n");
+ exit(1);
+ }
+ if (!ctx.conda.tool_build_version) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda-build version\n");
+ exit(1);
+ }
+ }
- if (!isempty(ctx.meta.based_on)) {
- msg(STASIS_MSG_L1, "Generating package overlay from environment: %s\n", env_name);
- if (delivery_overlay_packages_from_env(&ctx, env_name)) {
- msg(STASIS_MSG_L2 | STASIS_MSG_ERROR, "%s", "Failed to generate package overlay. Resulting environment integrity cannot be guaranteed.\n");
+ if (pip_exec("install build")) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "'build' tool installation failed\n");
exit(1);
}
- }
- msg(STASIS_MSG_L1, "Filter deliverable packages\n");
- delivery_defer_packages(&ctx, DEFER_CONDA);
- delivery_defer_packages(&ctx, DEFER_PIP);
+ if (!isempty(ctx.meta.based_on)) {
+ msg(STASIS_MSG_L1, "Generating package overlay from environment: %s\n", env_name);
+ if (delivery_overlay_packages_from_env(&ctx, env_name)) {
+ msg(STASIS_MSG_L2 | STASIS_MSG_ERROR, "%s", "Failed to generate package overlay. Resulting environment integrity cannot be guaranteed.\n");
+ exit(1);
+ }
+ }
+
+ msg(STASIS_MSG_L1, "Filter deliverable packages\n");
+ delivery_defer_packages(&ctx, DEFER_CONDA);
+ delivery_defer_packages(&ctx, DEFER_PIP);
+ } else {
+ delivery_conda_enable(&ctx, ctx.storage.conda_install_prefix);
+ }
msg(STASIS_MSG_L1, "Overview\n");
delivery_meta_show(&ctx);
@@ -466,79 +468,81 @@ int main(int argc, char *argv[]) {
}
- // Populate the release environment
- msg(STASIS_MSG_L1, "Populating release environment\n");
- msg(STASIS_MSG_L2, "Installing conda packages\n");
- if (strlist_count(ctx.conda.conda_packages)) {
- if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_CONDA, (struct StrList *[]) {ctx.conda.conda_packages, NULL})) {
- exit(1);
+ if (globals.enable_export) {
+ // Populate the release environment
+ msg(STASIS_MSG_L1, "Populating release environment\n");
+ msg(STASIS_MSG_L2, "Installing conda packages\n");
+ if (strlist_count(ctx.conda.conda_packages)) {
+ if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_CONDA, (struct StrList *[]) {ctx.conda.conda_packages, NULL})) {
+ exit(1);
+ }
}
- }
- if (strlist_count(ctx.conda.conda_packages_defer)) {
- msg(STASIS_MSG_L3, "Installing deferred conda packages\n");
- if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_CONDA | INSTALL_PKG_CONDA_DEFERRED, (struct StrList *[]) {ctx.conda.conda_packages_defer, NULL})) {
- exit(1);
+ if (strlist_count(ctx.conda.conda_packages_defer)) {
+ msg(STASIS_MSG_L3, "Installing deferred conda packages\n");
+ if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_CONDA | INSTALL_PKG_CONDA_DEFERRED, (struct StrList *[]) {ctx.conda.conda_packages_defer, NULL})) {
+ exit(1);
+ }
+ } else {
+ msg(STASIS_MSG_L3, "No deferred conda packages\n");
}
- } else {
- msg(STASIS_MSG_L3, "No deferred conda packages\n");
- }
- msg(STASIS_MSG_L2, "Installing pip packages\n");
- if (strlist_count(ctx.conda.pip_packages)) {
- if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_PIP, (struct StrList *[]) {ctx.conda.pip_packages, NULL})) {
- exit(1);
+ msg(STASIS_MSG_L2, "Installing pip packages\n");
+ if (strlist_count(ctx.conda.pip_packages)) {
+ if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_PIP, (struct StrList *[]) {ctx.conda.pip_packages, NULL})) {
+ exit(1);
+ }
}
- }
- if (strlist_count(ctx.conda.pip_packages_defer)) {
- msg(STASIS_MSG_L3, "Installing deferred pip packages\n");
- if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_PIP | INSTALL_PKG_PIP_DEFERRED, (struct StrList *[]) {ctx.conda.pip_packages_defer, NULL})) {
- exit(1);
+ if (strlist_count(ctx.conda.pip_packages_defer)) {
+ msg(STASIS_MSG_L3, "Installing deferred pip packages\n");
+ if (delivery_install_packages(&ctx, ctx.storage.conda_install_prefix, env_name, INSTALL_PKG_PIP | INSTALL_PKG_PIP_DEFERRED, (struct StrList *[]) {ctx.conda.pip_packages_defer, NULL})) {
+ exit(1);
+ }
+ } else {
+ msg(STASIS_MSG_L3, "No deferred pip packages\n");
}
- } else {
- msg(STASIS_MSG_L3, "No deferred pip packages\n");
- }
- conda_exec("list");
+ conda_exec("list");
- msg(STASIS_MSG_L1, "Creating release\n");
- msg(STASIS_MSG_L2, "Exporting delivery configuration\n");
- if (!pushd(ctx.storage.cfgdump_dir)) {
- char filename[PATH_MAX] = {0};
- sprintf(filename, "%s.ini", ctx.info.release_name);
- FILE *spec = fopen(filename, "w+");
- if (!spec) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", filename);
+ msg(STASIS_MSG_L1, "Creating release\n");
+ msg(STASIS_MSG_L2, "Exporting delivery configuration\n");
+ if (!pushd(ctx.storage.cfgdump_dir)) {
+ char filename[PATH_MAX] = {0};
+ sprintf(filename, "%s.ini", ctx.info.release_name);
+ FILE *spec = fopen(filename, "w+");
+ if (!spec) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", filename);
+ exit(1);
+ }
+ ini_write(ctx._stasis_ini_fp.delivery, &spec, INI_WRITE_RAW);
+ fclose(spec);
+
+ memset(filename, 0, sizeof(filename));
+ sprintf(filename, "%s-rendered.ini", ctx.info.release_name);
+ spec = fopen(filename, "w+");
+ if (!spec) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", filename);
+ exit(1);
+ }
+ ini_write(ctx._stasis_ini_fp.delivery, &spec, INI_WRITE_PRESERVE);
+ fclose(spec);
+ popd();
+ } else {
+ SYSERROR("Failed to enter directory: %s", ctx.storage.delivery_dir);
exit(1);
}
- ini_write(ctx._stasis_ini_fp.delivery, &spec, INI_WRITE_RAW);
- fclose(spec);
-
- memset(filename, 0, sizeof(filename));
- sprintf(filename, "%s-rendered.ini", ctx.info.release_name);
- spec = fopen(filename, "w+");
- if (!spec) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", filename);
+
+ msg(STASIS_MSG_L2, "Exporting %s\n", env_name_testing);
+ if (conda_env_export(env_name_testing, ctx.storage.delivery_dir, env_name_testing)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", env_name_testing);
exit(1);
}
- ini_write(ctx._stasis_ini_fp.delivery, &spec, INI_WRITE_PRESERVE);
- fclose(spec);
- popd();
- } else {
- SYSERROR("Failed to enter directory: %s", ctx.storage.delivery_dir);
- exit(1);
- }
- msg(STASIS_MSG_L2, "Exporting %s\n", env_name_testing);
- if (conda_env_export(env_name_testing, ctx.storage.delivery_dir, env_name_testing)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", env_name_testing);
- exit(1);
- }
-
- msg(STASIS_MSG_L2, "Exporting %s\n", env_name);
- if (conda_env_export(env_name, ctx.storage.delivery_dir, env_name)) {
- msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", env_name);
- exit(1);
+ msg(STASIS_MSG_L2, "Exporting %s\n", env_name);
+ if (conda_env_export(env_name, ctx.storage.delivery_dir, env_name)) {
+ msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed %s\n", env_name);
+ exit(1);
+ }
}
// Rewrite release environment output (i.e. set package origin(s) to point to the deployment server, etc.)
diff --git a/src/lib/core/include/core.h b/src/lib/core/include/core.h
index 362ac8d..6199635 100644
--- a/src/lib/core/include/core.h
+++ b/src/lib/core/include/core.h
@@ -45,6 +45,7 @@ struct STASIS_GLOBAL {
bool enable_overwrite; //!< Enable release file clobbering
bool enable_rewrite_spec_stage_2; //!< Enable automatic @STR@ replacement in output files
bool enable_parallel; //!< Enable testing in parallel
+ bool enable_export; //!< Enable environment creation
long cpu_limit; //!< Limit parallel processing to n cores (default: max - 1)
long parallel_fail_fast; //!< Fail immediately on error
int pool_status_interval; //!< Report "Task is running" every n seconds