From 7d675a70bf92bd1606f77fae01c6e56afdcee5ef Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Fri, 21 Jun 2024 12:36:55 -0400 Subject: Rebrand OhMyCal (OMC) as STASIS --- CMakeLists.txt | 6 +- README.md | 64 +- docs/Doxyfile | 2 +- docs/conf.py | 2 +- docs/environment.yaml | 1 - examples/template/example.ini | 4 +- include/artifactory.h | 18 +- include/conda.h | 8 +- include/config.h.in | 18 +- include/copy.h | 6 +- include/core.h | 99 ++ include/deliverable.h | 394 -------- include/delivery.h | 394 ++++++++ include/docker.h | 16 +- include/download.h | 6 +- include/environment.h | 6 +- include/ini.h | 6 +- include/junitxml.h | 6 +- include/omc.h | 99 -- include/os_darwin.h | 4 +- include/os_linux.h | 4 +- include/recipe.h | 6 +- include/relocation.h | 8 +- include/rules.h | 8 +- include/str.h | 24 +- include/strlist.h | 6 +- include/system.h | 8 +- include/template.h | 8 +- include/utils.h | 58 +- include/wheel.h | 6 +- omc.ini | 36 - src/CMakeLists.txt | 20 +- src/artifactory.c | 60 +- src/conda.c | 24 +- src/copy.c | 2 +- src/deliverable.c | 2100 ----------------------------------------- src/delivery.c | 2100 +++++++++++++++++++++++++++++++++++++++++ src/docker.c | 22 +- src/download.c | 2 +- src/environment.c | 6 +- src/globals.c | 31 +- src/ini.c | 12 +- src/omc_indexer.c | 753 --------------- src/omc_main.c | 612 ------------ src/recipe.c | 2 +- src/relocation.c | 4 +- src/stasis_indexer.c | 753 +++++++++++++++ src/stasis_main.c | 612 ++++++++++++ src/str.c | 12 +- src/system.c | 8 +- src/template.c | 2 +- src/utils.c | 52 +- stasis.ini | 36 + tests/CMakeLists.txt | 2 +- tests/test_docker.c | 40 +- tests/test_ini.c | 80 +- tests/test_relocation.c | 20 +- tests/test_str.c | 54 +- tests/test_strlist.c | 94 +- tests/test_system.c | 64 +- tests/test_template.c | 32 +- tests/test_utils.c | 132 +-- tests/testing.h | 86 +- 63 files changed, 4579 insertions(+), 4581 deletions(-) create mode 100644 include/core.h delete mode 100644 include/deliverable.h create mode 100644 include/delivery.h delete mode 100644 include/omc.h delete mode 100644 omc.ini delete mode 100644 src/deliverable.c create mode 100644 src/delivery.c delete mode 100644 src/omc_indexer.c delete mode 100644 src/omc_main.c create mode 100644 src/stasis_indexer.c create mode 100644 src/stasis_main.c create mode 100644 stasis.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index ca10017..f358b22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15) -project(OMC C) +project(STASIS C) include(GNUInstallDirs) set(nix_cflags -Wall -Wextra -fPIC) @@ -35,5 +35,5 @@ endif() set(SYSCONFDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/config.h @ONLY) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/omc.ini DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/omc) -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mission DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/omc) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/stasis.ini DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/stasis) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mission DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/stasis) diff --git a/README.md b/README.md index 8bb38ba..933bc71 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![CMake on multiple platforms](https://github.com/spacetelescope/ohmycal/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/spacetelescope/ohmycal/actions/workflows/cmake-multi-platform.yml) [![Documentation Status](https://readthedocs.org/projects/oh-my-cal/badge/?version=latest)](https://oh-my-cal.readthedocs.io/en/latest/?badge=latest) +[![CMake on multiple platforms](https://github.com/spacetelescope/stasis/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/spacetelescope/stasis/actions/workflows/cmake-multi-platform.yml) [![Documentation Status](https://readthedocs.org/projects/stasis-docs/badge/?version=latest)](https://stasis-docs.readthedocs.io/en/latest/?badge=latest) -OMC consolidate the steps required to build, test, and deploy calibration pipelines and other software suites maintained by engineers at the Space Telescope Science Institute. +STASIS consolidates the steps required to build, test, and deploy calibration pipelines and other software suites maintained by engineers at the Space Telescope Science Institute. # Requirements @@ -15,8 +15,8 @@ OMC consolidate the steps required to build, test, and deploy calibration pipeli Download the source code ```shell -git clone https://github.com/jhunkeler/ohmycal.git -cd ohmycal +git clone https://github.com/spacetelescope/stasis.git +cd stasis ``` Create and enter the build directory @@ -29,26 +29,26 @@ cd build Run cmake ```shell -cmake .. -DCMAKE_INSTALL_PREFIX="$HOME/programs/ohmycal" +cmake .. -DCMAKE_INSTALL_PREFIX="$HOME/programs/stasis" ``` -Compile and install ohmycal +Compile and install stasis ```shell make make install -export PATH="$HOME/programs/ohmycal/bin:$PATH" +export PATH="$HOME/programs/stasis/bin:$PATH" ``` # Quickstart ## Step 1: Create a mission -Missions definitions live in OMC's `etc/mission` directory. Let's create a new one specifically geared toward generic data analysis tools. You may override the path to the `etc` directory by setting the `OMC_SYSCONFDIR` environment variable to different location. +Missions definitions live in STASIS's `etc/mission` directory. Let's create a new one specifically geared toward generic data analysis tools. You may override the path to the `etc` directory by setting the `STASIS_SYSCONFDIR` environment variable to different location. ```shell -mkdir $HOME/programs/ohmycal/etc/missions/mymission -touch $HOME/programs/ohmycal/etc/missions/mymission/mymission.ini +mkdir $HOME/programs/stasis/etc/missions/mymission +touch $HOME/programs/stasis/etc/missions/mymission/mymission.ini ``` Now populate the new data analysis mission configuration. Refer to the [release formatters](#release-formatters) section to see a list of what each `%` formatter does. @@ -65,7 +65,7 @@ build_number_fmt = %v.%r ; e.g. 1.2.3.1 ``` -Text files containing OMC template strings can be stored at the same level as the mission configuration, and rendered to anywhere inside the output directory. This will can you time if you plan to release for multiple platforms and architectures. +Text files containing STASIS template strings can be stored at the same level as the mission configuration, and rendered to anywhere inside the output directory. This will can you time if you plan to release for multiple platforms and architectures. ```ini [template:readme.md.in] @@ -79,7 +79,7 @@ destination = {{ storage.build_docker_dir }}/Dockerfile ## Step 2: Create a delivery configuration -OMC's configuration parser does not distinguish input files by extension, so `mydelivery.ini`, `mydelivery.cfg`, `mydelivery.txt`, and `abc123.zyx987` are all perfectly valid file names. +STASIS's configuration parser does not distinguish input files by extension, so `mydelivery.ini`, `mydelivery.cfg`, `mydelivery.txt`, and `abc123.zyx987` are all perfectly valid file names. All deliveries require a `[meta]` section. Here global metadata such as the delivery's `name`, `version`, and any programs/dependencies that make up the deliverable. @@ -92,15 +92,15 @@ rc = 1 python = 3.12 ``` -The `[conda]` section instructs OMC how to obtain the conda installer of your choice, and defines the packages to be installed into the delivery's release environment. +The `[conda]` section instructs STASIS how to obtain the conda installer of your choice, and defines the packages to be installed into the delivery's release environment. ```ini [conda] ; e.g. Download Miniforge3-23.11.0-0 for the current system platform and architecture installer_name = Miniforge3 installer_version = 23.11.0-0 -installer_platform = {{env:OMC_CONDA_PLATFORM}} -installer_arch = {{env:OMC_CONDA_ARCH}} +installer_platform = {{env:STASIS_CONDA_PLATFORM}} +installer_arch = {{env:STASIS_CONDA_ARCH}} installer_baseurl = https://github.com/conda-forge/miniforge/releases/download/{{conda.installer_version}} conda_packages = @@ -133,10 +133,10 @@ script = tests/ ``` -## Step 3: Run OMC +## Step 3: Run STASIS ```shell -omc mydelivery.ini +stasis mydelivery.ini ``` # Configuration @@ -146,17 +146,17 @@ omc mydelivery.ini | Name | Purpose | |------------------------------|-------------------------------------------------------| | TMPDIR | Change default path to store temporary data | -| OMC_ROOT | Change default path to write OMC's data | -| OMC_SYSCONFDIR | Change default path to search for configuration files | -| OMC_JF_ARTIFACTORY_URL | Artifactory service URL (ending in `/artifactory`) | -| OMC_JF_ACCESS_TOKEN | Artifactory Access Token | -| OMC_JF_USER | Artifactory username | -| OMC_JF_PASSWORD | Artifactory password | -| OMC_JF_SSH_KEY_PATH | Path to SSH public key file | -| OMC_JF_SSH_PASSPHRASE | Password associated with SSH public key file | -| OMC_JF_CLIENT_CERT_CERT_PATH | Path to OpenSSL cert files | -| OMC_JF_CLIENT_CERT_KEY_PATH | OpenSSL key file (in cert path) | -| OMC_JF_REPO | Artifactory "generic" repository to write to | +| STASIS_ROOT | Change default path to write STASIS's data | +| STASIS_SYSCONFDIR | Change default path to search for configuration files | +| STASIS_JF_ARTIFACTORY_URL | Artifactory service URL (ending in `/artifactory`) | +| STASIS_JF_ACCESS_TOKEN | Artifactory Access Token | +| STASIS_JF_USER | Artifactory username | +| STASIS_JF_PASSWORD | Artifactory password | +| STASIS_JF_SSH_KEY_PATH | Path to SSH public key file | +| STASIS_JF_SSH_PASSPHRASE | Password associated with SSH public key file | +| STASIS_JF_CLIENT_CERT_CERT_PATH | Path to OpenSSL cert files | +| STASIS_JF_CLIENT_CERT_KEY_PATH | OpenSSL key file (in cert path) | +| STASIS_JF_REPO | Artifactory "generic" repository to write to | # Variable expansion @@ -236,7 +236,7 @@ All configuration section names and keys are _case-sensitive_. ### runtime -Environment variables exported are _global_ to all programs executed by ohmycal. There is no limit to the number of environment variables that can be set. +Environment variables exported are _global_ to all programs executed by stasis. There is no limit to the number of environment variables that can be set. | Key | Type | Purpose | Required | |------------------------|--------|------------------------------------------------------------|----------| @@ -245,7 +245,7 @@ Environment variables exported are _global_ to all programs executed by ohmycal. ### test:_name_ -Sections starting with `test:` will be used during the testing phase of the ohmycal pipeline. Where the value of `name` following the colon is an arbitrary value, and only used for reporting which test-run is executing. Section names must be unique. +Sections starting with `test:` will be used during the testing phase of the stasis pipeline. Where the value of `name` following the colon is an arbitrary value, and only used for reporting which test-run is executing. Section names must be unique. | Key | Type | Purpose | Required | |--------------|--------|-------------------------------------------------------|----------| @@ -277,10 +277,10 @@ The `deploy:docker` section controls how Docker images are created, when a `Dock # Mission files -Mission rules are defined in the `OMC_SYCONFDIR/mission` directory. Each mission configuration file shares the same name as the directory. To create a new mission, `example`, the directory structure will be as follows: +Mission rules are defined in the `STASIS_SYCONFDIR/mission` directory. Each mission configuration file shares the same name as the directory. To create a new mission, `example`, the directory structure will be as follows: ```text -OMC_SYSCONFDIR/ +STASIS_SYSCONFDIR/ mission/ example/ example.ini diff --git a/docs/Doxyfile b/docs/Doxyfile index cacb2f9..92fcec2 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -4,7 +4,7 @@ # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "Oh My Cal" +PROJECT_NAME = "STASIS" PROJECT_NUMBER = PROJECT_BRIEF = "A pipeline delivery generator" PROJECT_LOGO = logo.png diff --git a/docs/conf.py b/docs/conf.py index 6610bbd..93d31a8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,7 @@ subprocess.run("doxygen", shell=True) # -- Project information ----------------------------------------------------- -project = 'Oh My Cal' +project = 'STASIS' copyright = '2023-2024, Space Telescope Science Institute' author = 'Joseph Hunkeler' diff --git a/docs/environment.yaml b/docs/environment.yaml index fed35ba..fe5f8cd 100644 --- a/docs/environment.yaml +++ b/docs/environment.yaml @@ -1,7 +1,6 @@ name: RTD channels: - conda-forge - - defaults dependencies: - python=3.11 - doxygen diff --git a/examples/template/example.ini b/examples/template/example.ini index e84cf67..4a4c579 100644 --- a/examples/template/example.ini +++ b/examples/template/example.ini @@ -28,8 +28,8 @@ python = [conda] installer_name = Miniforge3 installer_version = 23.3.1-1 -installer_platform = ${OMC_CONDA_PLATFORM} -installer_arch = ${OMC_CONDA_ARCH} +installer_platform = ${STASIS_CONDA_PLATFORM} +installer_arch = ${STASIS_CONDA_ARCH} installer_baseurl = https://github.com/conda-forge/miniforge/releases/latest/download ; (list) Conda packages to install diff --git a/include/artifactory.h b/include/artifactory.h index 0883eef..6880d17 100644 --- a/include/artifactory.h +++ b/include/artifactory.h @@ -1,10 +1,10 @@ //! @file artifactory.h -#ifndef OMC_ARTIFACTORY_H -#define OMC_ARTIFACTORY_H +#ifndef STASIS_ARTIFACTORY_H +#define STASIS_ARTIFACTORY_H #include #include -#include "omc.h" +#include "core.h" //! JFrog Artifactory Authentication struct struct JFRT_Auth { @@ -276,13 +276,13 @@ int jfrog_cli_rt_build_collect_env(struct JFRT_Auth *auth, char *build_name, cha int jfrog_cli_rt_build_publish(struct JFRT_Auth *auth, char *build_name, char *build_number); /** - * Configure JFrog CLI authentication according to OMC specs + * Configure JFrog CLI authentication according to STASIS specs * - * This function will use the OMC_JF_* environment variables to configure the authentication - * context. With this in mind, if an OMC_JF_* environment variable is not defined, the original value of + * This function will use the STASIS_JF_* environment variables to configure the authentication + * context. With this in mind, if an STASIS_JF_* environment variable is not defined, the original value of * the structure member will be used instead. * - * Use OMC_JF_* variables to configure context + * Use STASIS_JF_* variables to configure context * * ```c * struct JFRT_Auth auth_ctx; @@ -299,7 +299,7 @@ int jfrog_cli_rt_build_publish(struct JFRT_Auth *auth, char *build_name, char *b * jfrt_auth_init(&auth_ctx); * ``` * - * Use your own input without OMC's help. Purely an illustrative example. + * Use your own input without STASIS's help. Purely an illustrative example. * * ```c * struct JFRT_Auth auth_ctx; @@ -320,4 +320,4 @@ int jfrt_auth_init(struct JFRT_Auth *auth_ctx); */ void jfrt_upload_init(struct JFRT_Upload *ctx); -#endif //OMC_ARTIFACTORY_H \ No newline at end of file +#endif //STASIS_ARTIFACTORY_H \ No newline at end of file diff --git a/include/conda.h b/include/conda.h index 9cbbf9c..086a842 100644 --- a/include/conda.h +++ b/include/conda.h @@ -1,10 +1,10 @@ //! @file conda.h -#ifndef OMC_CONDA_H -#define OMC_CONDA_H +#ifndef STASIS_CONDA_H +#define STASIS_CONDA_H #include #include -#include "omc.h" +#include "core.h" #define CONDA_INSTALL_PREFIX "conda" @@ -167,4 +167,4 @@ int conda_env_export(char *name, char *output_dir, char *output_filename); * @return exit code from "conda" */ int conda_index(const char *path); -#endif //OMC_CONDA_H +#endif //STASIS_CONDA_H diff --git a/include/config.h.in b/include/config.h.in index fe6beb2..0df955d 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -1,28 +1,28 @@ -#ifndef OMC_CONFIG_H -#define OMC_CONFIG_H +#ifndef STASIS_CONFIG_H +#define STASIS_CONFIG_H -#define OMC_SYSCONFDIR "@SYSCONFDIR@/omc" +#define STASIS_SYSCONFDIR "@SYSCONFDIR@/stasis" #if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) -#define OMC_OS_WINDOWS +#define STASIS_OS_WINDOWS #elif defined(__linux__) -#define OMC_OS_LINUX +#define STASIS_OS_LINUX #include "os_linux.h" #elif defined(unix) || defined(__unix__) || defined(__unix) -#define OMC_OS_UNIX +#define STASIS_OS_UNIX #elif defined(__APPLE__) -#define OMC_OS_DARWIN +#define STASIS_OS_DARWIN #include "os_darwin.h" #else -#define OMC_OS_UNKNOWN +#define STASIS_OS_UNKNOWN #error Operating system unknown/unsupported #endif // OS detection -#endif // OMC_CONFIG_H +#endif // STASIS_CONFIG_H diff --git a/include/copy.h b/include/copy.h index 5a53d7d..609a6cf 100644 --- a/include/copy.h +++ b/include/copy.h @@ -1,5 +1,5 @@ //! @file copy.h -#ifndef OMC_COPY_H +#ifndef STASIS_COPY_H #include #include @@ -8,7 +8,7 @@ #include #include #include -#include "omc.h" +#include "core.h" #define CT_OWNER 1 << 1 #define CT_PERM 1 << 2 @@ -58,4 +58,4 @@ int mkdirs(const char *_path, mode_t mode); */ int copy2(const char *src, const char *dest, unsigned op); -#endif // OMC_COPY_H \ No newline at end of file +#endif // STASIS_COPY_H \ No newline at end of file diff --git a/include/core.h b/include/core.h new file mode 100644 index 0000000..1a7ddea --- /dev/null +++ b/include/core.h @@ -0,0 +1,99 @@ +//! @file stasis.h +#ifndef STASIS_CORE_H +#define STASIS_CORE_H + +#include +#include +#include +#include +#include +#include +#include + +#define SYSERROR(MSG, ...) do { \ + fprintf(stderr, "%s:%s:%d:%s - ", path_basename(__FILE__), __FUNCTION__, __LINE__, strerror(errno) ? "info" : strerror(errno)); \ + fprintf(stderr, MSG LINE_SEP, __VA_ARGS__); \ +} while (0) +#define STASIS_BUFSIZ 8192 +#define STASIS_NAME_MAX 255 +#define STASIS_DIRSTACK_MAX 1024 +#define STASIS_TIME_STR_MAX 128 +#define HTTP_ERROR(X) X >= 400 + +#include "config.h" +#include "template.h" +#include "utils.h" +#include "copy.h" +#include "ini.h" +#include "conda.h" +#include "environment.h" +#include "artifactory.h" +#include "docker.h" +#include "delivery.h" +#include "str.h" +#include "strlist.h" +#include "system.h" +#include "download.h" +#include "recipe.h" +#include "relocation.h" +#include "wheel.h" +#include "junitxml.h" + +#define guard_runtime_free(X) do { if (X) { runtime_free(X); X = NULL; } } while (0) +#define guard_strlist_free(X) do { if ((*X)) { strlist_free(X); (*X) = NULL; } } while (0) +#define guard_free(X) do { if (X) { free(X); X = NULL; } } while (0) +#define GENERIC_ARRAY_FREE(ARR) do { \ + for (size_t ARR_I = 0; ARR && ARR[ARR_I] != NULL; ARR_I++) { \ + guard_free(ARR[ARR_I]); \ + } \ + guard_free(ARR); \ +} while (0) + +#define COE_CHECK_ABORT(COND, MSG) \ + do {\ + if (!globals.continue_on_error && COND) { \ + msg(STASIS_MSG_ERROR, MSG ": Aborting execution (--continue-on-error/-C is not enabled)\n"); \ + exit(1); \ + } \ + } while (0) + +struct STASIS_GLOBAL { + bool verbose; //!< Enable verbose output + bool always_update_base_environment; //!< Update base environment immediately after activation + bool continue_on_error; //!< Do not stop on test failures + bool conda_fresh_start; //!< Always install a new copy of Conda + bool enable_docker; //!< Enable docker image builds + bool enable_artifactory; //!< Enable artifactory uploads + bool enable_testing; //!< Enable package testing + struct StrList *conda_packages; //!< Conda packages to install after initial activation + struct StrList *pip_packages; //!< Pip packages to install after initial activation + char *tmpdir; //!< Path to temporary storage directory + char *conda_install_prefix; //!< Path to install conda + char *sysconfdir; //!< Path where STASIS reads its configuration files (mission directory, etc) + struct { + char *tox_posargs; + } workaround; + struct Jfrog { + char *jfrog_artifactory_base_url; + char *jfrog_artifactory_product; + char *cli_major_ver; + char *version; + char *os; + char *arch; + char *remote_filename; + char *repo; + char *url; + } jfrog; +}; +extern struct STASIS_GLOBAL globals; +extern const char *VERSION; +extern const char *AUTHOR; +extern const char *BANNER; + + +/** + * Free memory allocated in global configuration structure + */ +void globals_free(); + +#endif //STASIS_CORE_H diff --git a/include/deliverable.h b/include/deliverable.h deleted file mode 100644 index effd098..0000000 --- a/include/deliverable.h +++ /dev/null @@ -1,394 +0,0 @@ -/// @file deliverable.h - -#ifndef OMC_DELIVERABLE_H -#define OMC_DELIVERABLE_H - -#include -#include -#include -#include -#include "omc.h" - -#define DELIVERY_PLATFORM_MAX 4 -#define DELIVERY_PLATFORM_MAXLEN 65 -#define DELIVERY_PLATFORM 0 -#define DELIVERY_PLATFORM_CONDA_SUBDIR 1 -#define DELIVERY_PLATFORM_CONDA_INSTALLER 2 -#define DELIVERY_PLATFORM_RELEASE 3 - -#define DELIVERY_REWRITE_SPEC_STAGE_1 0 -#define DELIVERY_REWRITE_SPEC_STAGE_2 1 - -#define INSTALL_PKG_CONDA 1 << 1 ///< Toggle conda package installation -#define INSTALL_PKG_CONDA_DEFERRED 1 << 2 ///< Toggle deferred conda package installation -#define INSTALL_PKG_PIP 1 << 3 ///< Toggle pip package installation -#define INSTALL_PKG_PIP_DEFERRED 1 << 4 ///< Toggle deferred package installation from source - -#define DEFER_CONDA 0 ///< Build conda packages -#define DEFER_PIP 1 ///< Build python packages - -struct Content { - unsigned type; - char *filename; - char *data; -}; - -/*! \struct Delivery - * \brief A structure describing a full delivery object - */ -struct Delivery { - /*! \struct OMC_INI_FP - * \brief Container for INIFILE handles - */ - struct OMC_INI_FP { - struct INIFILE *delivery; - struct INIFILE *cfg; - struct INIFILE *mission; - char *delivery_path; - char *cfg_path; - char *mission_path; - } _omc_ini_fp; - - /*! \struct System - * \brief System information - */ - struct System { - char *arch; - ///< System CPU architecture ident - char **platform; - ///< System platform name - } system; - /*! \struct Storage - * \brief Storage paths - */ - struct Storage { - char *root; ///< Top-level storage area - char *tmpdir; ///< Temporary storage area (within root) - char *output_dir; ///< Base path to where all artifacts are stored - char *delivery_dir; ///< Delivery artifact output directory - char *cfgdump_dir; ///< Base path to where input configuration dumps are stored - char *tools_dir; ///< Tools storage - char *mission_dir; ///< Mission data storage - char *package_dir; ///< Base path to where all packages are stored - char *results_dir; ///< Base path to where test results are stored - char *meta_dir; ///< Base path to where metadata records are stored - char *conda_install_prefix; ///< Path to install Conda - char *conda_artifact_dir; ///< Base path to store compiled conda packages - char *conda_staging_dir; ///< Base path to copy compiled conda packages - char *conda_staging_url; ///< URL to access compiled conda packages - char *docker_artifact_dir; ///< Base path to store saved docker images - char *wheel_artifact_dir; ///< Base path to store compiled wheel packages (Unused) - char *wheel_staging_dir; ///< Base path to copy compiled wheel packages (Unused) - char *wheel_staging_url; ///< URL to access compiled wheel packages (Unused) - char *build_dir; ///< Base path to store source code and recipes - char *build_recipes_dir; ///< Path to store conda recipes - char *build_sources_dir; ///< Path to store source code - char *build_testing_dir; ///< Path to store test data (Unused) - char *build_docker_dir; ///< Path to store docker build script - } storage; - - /*! \struct Meta - * \brief Metadata related to the delivery - */ - struct Meta { - char *name; ///< delivery name - char *version; ///< delivery version - int rc; ///< build iteration - char *python; ///< version of python to use - char *python_compact; ///< shortened python identifier - char *based_on; ///< URL to previous final configuration - char *mission; ///< hst, jwst, roman - char *codename; ///< HST uses codenames - bool final; ///< is this a final release? - } meta; - - /*! \struct Info - * \brief Release information (name & datetime) - */ - struct Info { - char *release_name; ///< The fully combined release string - char *build_name; - char *build_number; - struct tm *time_info; ///< Delivery time structure - time_t time_now; ///< Time stamp for when OMC execution started - char *time_str_epoch; ///< String representation of Unix epoch - } info; - - /*! \struct Conda - * \brief Conda configuration - * - * This includes lists describing packages to be delivered - */ - struct Conda { - char *installer_baseurl; ///< URL describing where Conda will be downloaded from - char *installer_name; ///< Name of installer (Miniconda3, Miniforge3, etc) - char *installer_version; ///< Version of installer - char *installer_platform; ///< Platform/OS target of installer - char *installer_arch; ///< CPU architecture target of installer - char *installer_path; ///< Absolute path of installer on-disk - char *tool_version; ///< Installed version of conda - char *tool_build_version; ///< Installed version of "build" package - struct StrList *conda_packages; ///< Conda packages to deliver - struct StrList *conda_packages_defer; ///< Conda recipes to be built for delivery - struct StrList *pip_packages; ///< Python packages to install (pip) - struct StrList *pip_packages_defer; ///< Python packages to be built for delivery - struct StrList *wheels_packages; ///< Wheel packages built for delivery - } conda; - - /*! \struct Runtime - * \brief Global runtime variables - */ - struct Runtime { - 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; ///< Commands to execute - 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 Deploy { - struct JFRT_Auth jfrog_auth; - - struct JFrog { - struct StrList *files; - struct JFRT_Upload upload_ctx; - char *repo; - char *dest; - } jfrog[1000]; - - struct Docker { - struct DockerCapabilities capabilities; - char *image_compression; - char *dockerfile; - char *registry; - char *test_script; - struct StrList *build_args; - struct StrList *tags; - } docker; - } deploy; - - struct Rule { - struct INIFILE *_handle; - bool enable_final; ///< true=allow rc value replacement, false=keep rc value even if final release - char *release_fmt; ///< Release format string - char *build_name_fmt; ///< Build name format string - char *build_number_fmt; ///< Build number format string - struct Content content[1000]; - } rules; -}; - -/** - * Initializes a Deliver structure - * @param ctx pointer to Delivery context - * @param ini pointer to INIFILE describing a delivery - * @param cfg pointer to INIFILE describing extra configuration data - * @return `0` on success - * @return Non-zero on error - */ -int delivery_init(struct Delivery *ctx); - -/** - * Free memory allocated by delivery_init() - * @param ctx pointer to Delivery context - */ -void delivery_free(struct Delivery *ctx); - -/** - * Print Delivery metadata - * @param ctx pointer to Delivery context - */ -void delivery_meta_show(struct Delivery *ctx); - -/** - * Print Delivery conda configuration - * @param ctx pointer to Delivery context - */ -void delivery_conda_show(struct Delivery *ctx); - -/** - * Print Delivery tests - * @param ctx pointer to Delivery context - */ -void delivery_tests_show(struct Delivery *ctx); - -/** - * Print Delivery initial runtime environment - * @param ctx pointner to Delivery context - */ -void delivery_runtime_show(struct Delivery *ctx); - -/** - * Build Conda recipes associated with the Delivery - * @param ctx pointer to Delivery context - * @return 0 on success - * @return Non-zero on error - */ -int delivery_build_recipes(struct Delivery *ctx); - -/** - * Produce a list of wheels built for the Delivery (Unused) - * @param ctx pointer to Delivery context - * @return pointer to StrList - * @return NULL on error - */ -struct StrList *delivery_build_wheels(struct Delivery *ctx); - -/** - * Copy wheel packages to artifact storage - * @param ctx pointer to Delivery context - * @return 0 on success - * @return Non-zero on error - */ -int delivery_index_wheel_artifacts(struct Delivery *ctx); - -/** - * Generate a header block that is applied to delivery artifacts - * @param ctx pointer to Delivery context - * @return header on success - * @return NULL on error - */ -char *delivery_get_release_header(struct Delivery *ctx); - -/** - * Finalizes a delivery artifact for distribution - * @param ctx poitner to Delivery context - * @param filename path to delivery artifact (Conda YAML file) - */ -void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage); - -/** - * Copy compiled wheels to artifact storage - * @param ctx pointer to Delivery context - * @return 0 on success - * @return Non-zero on error - */ -int delivery_copy_wheel_artifacts(struct Delivery *ctx); - -/** - * Copy built Conda packages to artifact storage - * @param ctx poitner to Delivery context - * @return 0 on success - * @return Non-zero on error - */ -int delivery_copy_conda_artifacts(struct Delivery *ctx); - -/** - * Retrieve Conda installer - * @param installer_url URL to installation script - */ -int delivery_get_installer(struct Delivery *ctx, char *installer_url); - -/** - * Generate URL based on Delivery context - * @param ctx pointer to Delivery context - * @param result pointer to char - * @return in result - */ -void delivery_get_installer_url(struct Delivery *ctx, char *result); - -/** - * Install packages based on Delivery context - * @param ctx pointer to Delivery context - * @param conda_install_dir path to install Conda - * @param env_name name of Conda environment to create - * @param type INSTALL_PKG_CONDA - * @param type INSTALL_PKG_CONDA_DEFERRED - * @param type INSTALL_PKG_PIP - * @param type INSTALL_PKG_PIP_DEFERRED - * @param manifest pointer to array of StrList (package list(s)) - */ -int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, char *env_name, int type, struct StrList *manifest[]); - -/** - * Update "conda index" on Conda artifact storage - * @param ctx pointer to Delivery context - * @return 0 on success - * @return Non-zero on error - */ -int delivery_index_conda_artifacts(struct Delivery *ctx); - -/** - * Execute Delivery test array - * @param ctx pointer to Delivery context - */ -void delivery_tests_run(struct Delivery *ctx); - -/** - * Determine which packages are to be installed directly from conda or pip, - * and which packages need to be built locally - * @param ctx pointer to Delivery context - * @param type DEFER_CONDA (filter conda packages) - * @param type DEFER_PIP (filter python packages) - */ -void delivery_defer_packages(struct Delivery *ctx, int type); - -/** - * Configure and activate a Conda installation based on Delivery context - * @param ctx pointer to Delivery context - * @param conda_install_dir path to Conda installation - */ -void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir); - -/** - * Install Conda - * @param install_script path to Conda installation script - * @param conda_install_dir path to install Conda - */ -void delivery_install_conda(char *install_script, char *conda_install_dir); - -/** - * Generate a formatted release string - * - * Formatters: - * - * - `%%n` = Delivery Name - * - `%%c` = Delivery Codename (HST mission, only) - * - `%%m` = Mission - * - `%%R` = Delivery Revision number (or "final") - * - `%%r` = Delivery Revision number - * - `%%v` = Delivery Version - * - `%%P` = Python version (i.e. 3.9.1) - * - `%%p` = Compact Python version (i.e. 3.9.1 -> 39) - * - `%%a` = System architecture name - * - `%%o` = System platform name - * - `%%t` = Delivery timestamp (Unix Epoch) - * - * @param ctx pointer to Delivery context - * @param dest NULL pointer to string, or initialized string - * @param fmt release format string - * @return 0 on success, -1 on error - */ -int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt); - -// helper function -void delivery_gather_tool_versions(struct Delivery *ctx); - -// helper function -int delivery_init_tmpdir(struct Delivery *ctx); - -int delivery_init_artifactory(struct Delivery *ctx); - -int delivery_artifact_upload(struct Delivery *ctx); - -int delivery_mission_render_files(struct Delivery *ctx); - -int delivery_docker(struct Delivery *ctx); - -int delivery_fixup_test_results(struct Delivery *ctx); - -int *bootstrap_build_info(struct Delivery *ctx); - -int delivery_dump_metadata(struct Delivery *ctx); - -#endif //OMC_DELIVERABLE_H diff --git a/include/delivery.h b/include/delivery.h new file mode 100644 index 0000000..971705f --- /dev/null +++ b/include/delivery.h @@ -0,0 +1,394 @@ +/// @file delivery.h + +#ifndef STASIS_DELIVERY_H +#define STASIS_DELIVERY_H + +#include +#include +#include +#include +#include "core.h" + +#define DELIVERY_PLATFORM_MAX 4 +#define DELIVERY_PLATFORM_MAXLEN 65 +#define DELIVERY_PLATFORM 0 +#define DELIVERY_PLATFORM_CONDA_SUBDIR 1 +#define DELIVERY_PLATFORM_CONDA_INSTALLER 2 +#define DELIVERY_PLATFORM_RELEASE 3 + +#define DELIVERY_REWRITE_SPEC_STAGE_1 0 +#define DELIVERY_REWRITE_SPEC_STAGE_2 1 + +#define INSTALL_PKG_CONDA 1 << 1 ///< Toggle conda package installation +#define INSTALL_PKG_CONDA_DEFERRED 1 << 2 ///< Toggle deferred conda package installation +#define INSTALL_PKG_PIP 1 << 3 ///< Toggle pip package installation +#define INSTALL_PKG_PIP_DEFERRED 1 << 4 ///< Toggle deferred package installation from source + +#define DEFER_CONDA 0 ///< Build conda packages +#define DEFER_PIP 1 ///< Build python packages + +struct Content { + unsigned type; + char *filename; + char *data; +}; + +/*! \struct Delivery + * \brief A structure describing a full delivery object + */ +struct Delivery { + /*! \struct STASIS_INI_FP + * \brief Container for INIFILE handles + */ + struct STASIS_INI_FP { + struct INIFILE *delivery; + struct INIFILE *cfg; + struct INIFILE *mission; + char *delivery_path; + char *cfg_path; + char *mission_path; + } _stasis_ini_fp; + + /*! \struct System + * \brief System information + */ + struct System { + char *arch; + ///< System CPU architecture ident + char **platform; + ///< System platform name + } system; + /*! \struct Storage + * \brief Storage paths + */ + struct Storage { + char *root; ///< Top-level storage area + char *tmpdir; ///< Temporary storage area (within root) + char *output_dir; ///< Base path to where all artifacts are stored + char *delivery_dir; ///< Delivery artifact output directory + char *cfgdump_dir; ///< Base path to where input configuration dumps are stored + char *tools_dir; ///< Tools storage + char *mission_dir; ///< Mission data storage + char *package_dir; ///< Base path to where all packages are stored + char *results_dir; ///< Base path to where test results are stored + char *meta_dir; ///< Base path to where metadata records are stored + char *conda_install_prefix; ///< Path to install Conda + char *conda_artifact_dir; ///< Base path to store compiled conda packages + char *conda_staging_dir; ///< Base path to copy compiled conda packages + char *conda_staging_url; ///< URL to access compiled conda packages + char *docker_artifact_dir; ///< Base path to store saved docker images + char *wheel_artifact_dir; ///< Base path to store compiled wheel packages (Unused) + char *wheel_staging_dir; ///< Base path to copy compiled wheel packages (Unused) + char *wheel_staging_url; ///< URL to access compiled wheel packages (Unused) + char *build_dir; ///< Base path to store source code and recipes + char *build_recipes_dir; ///< Path to store conda recipes + char *build_sources_dir; ///< Path to store source code + char *build_testing_dir; ///< Path to store test data (Unused) + char *build_docker_dir; ///< Path to store docker build script + } storage; + + /*! \struct Meta + * \brief Metadata related to the delivery + */ + struct Meta { + char *name; ///< delivery name + char *version; ///< delivery version + int rc; ///< build iteration + char *python; ///< version of python to use + char *python_compact; ///< shortened python identifier + char *based_on; ///< URL to previous final configuration + char *mission; ///< hst, jwst, roman + char *codename; ///< HST uses codenames + bool final; ///< is this a final release? + } meta; + + /*! \struct Info + * \brief Release information (name & datetime) + */ + struct Info { + char *release_name; ///< The fully combined release string + char *build_name; + char *build_number; + struct tm *time_info; ///< Delivery time structure + time_t time_now; ///< Time stamp for when STASIS execution started + char *time_str_epoch; ///< String representation of Unix epoch + } info; + + /*! \struct Conda + * \brief Conda configuration + * + * This includes lists describing packages to be delivered + */ + struct Conda { + char *installer_baseurl; ///< URL describing where Conda will be downloaded from + char *installer_name; ///< Name of installer (Miniconda3, Miniforge3, etc) + char *installer_version; ///< Version of installer + char *installer_platform; ///< Platform/OS target of installer + char *installer_arch; ///< CPU architecture target of installer + char *installer_path; ///< Absolute path of installer on-disk + char *tool_version; ///< Installed version of conda + char *tool_build_version; ///< Installed version of "build" package + struct StrList *conda_packages; ///< Conda packages to deliver + struct StrList *conda_packages_defer; ///< Conda recipes to be built for delivery + struct StrList *pip_packages; ///< Python packages to install (pip) + struct StrList *pip_packages_defer; ///< Python packages to be built for delivery + struct StrList *wheels_packages; ///< Wheel packages built for delivery + } conda; + + /*! \struct Runtime + * \brief Global runtime variables + */ + struct Runtime { + 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; ///< Commands to execute + 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 Deploy { + struct JFRT_Auth jfrog_auth; + + struct JFrog { + struct StrList *files; + struct JFRT_Upload upload_ctx; + char *repo; + char *dest; + } jfrog[1000]; + + struct Docker { + struct DockerCapabilities capabilities; + char *image_compression; + char *dockerfile; + char *registry; + char *test_script; + struct StrList *build_args; + struct StrList *tags; + } docker; + } deploy; + + struct Rule { + struct INIFILE *_handle; + bool enable_final; ///< true=allow rc value replacement, false=keep rc value even if final release + char *release_fmt; ///< Release format string + char *build_name_fmt; ///< Build name format string + char *build_number_fmt; ///< Build number format string + struct Content content[1000]; + } rules; +}; + +/** + * Initializes a Deliver structure + * @param ctx pointer to Delivery context + * @param ini pointer to INIFILE describing a delivery + * @param cfg pointer to INIFILE describing extra configuration data + * @return `0` on success + * @return Non-zero on error + */ +int delivery_init(struct Delivery *ctx); + +/** + * Free memory allocated by delivery_init() + * @param ctx pointer to Delivery context + */ +void delivery_free(struct Delivery *ctx); + +/** + * Print Delivery metadata + * @param ctx pointer to Delivery context + */ +void delivery_meta_show(struct Delivery *ctx); + +/** + * Print Delivery conda configuration + * @param ctx pointer to Delivery context + */ +void delivery_conda_show(struct Delivery *ctx); + +/** + * Print Delivery tests + * @param ctx pointer to Delivery context + */ +void delivery_tests_show(struct Delivery *ctx); + +/** + * Print Delivery initial runtime environment + * @param ctx pointner to Delivery context + */ +void delivery_runtime_show(struct Delivery *ctx); + +/** + * Build Conda recipes associated with the Delivery + * @param ctx pointer to Delivery context + * @return 0 on success + * @return Non-zero on error + */ +int delivery_build_recipes(struct Delivery *ctx); + +/** + * Produce a list of wheels built for the Delivery (Unused) + * @param ctx pointer to Delivery context + * @return pointer to StrList + * @return NULL on error + */ +struct StrList *delivery_build_wheels(struct Delivery *ctx); + +/** + * Copy wheel packages to artifact storage + * @param ctx pointer to Delivery context + * @return 0 on success + * @return Non-zero on error + */ +int delivery_index_wheel_artifacts(struct Delivery *ctx); + +/** + * Generate a header block that is applied to delivery artifacts + * @param ctx pointer to Delivery context + * @return header on success + * @return NULL on error + */ +char *delivery_get_release_header(struct Delivery *ctx); + +/** + * Finalizes a delivery artifact for distribution + * @param ctx poitner to Delivery context + * @param filename path to delivery artifact (Conda YAML file) + */ +void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage); + +/** + * Copy compiled wheels to artifact storage + * @param ctx pointer to Delivery context + * @return 0 on success + * @return Non-zero on error + */ +int delivery_copy_wheel_artifacts(struct Delivery *ctx); + +/** + * Copy built Conda packages to artifact storage + * @param ctx poitner to Delivery context + * @return 0 on success + * @return Non-zero on error + */ +int delivery_copy_conda_artifacts(struct Delivery *ctx); + +/** + * Retrieve Conda installer + * @param installer_url URL to installation script + */ +int delivery_get_installer(struct Delivery *ctx, char *installer_url); + +/** + * Generate URL based on Delivery context + * @param ctx pointer to Delivery context + * @param result pointer to char + * @return in result + */ +void delivery_get_installer_url(struct Delivery *ctx, char *result); + +/** + * Install packages based on Delivery context + * @param ctx pointer to Delivery context + * @param conda_install_dir path to install Conda + * @param env_name name of Conda environment to create + * @param type INSTALL_PKG_CONDA + * @param type INSTALL_PKG_CONDA_DEFERRED + * @param type INSTALL_PKG_PIP + * @param type INSTALL_PKG_PIP_DEFERRED + * @param manifest pointer to array of StrList (package list(s)) + */ +int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, char *env_name, int type, struct StrList *manifest[]); + +/** + * Update "conda index" on Conda artifact storage + * @param ctx pointer to Delivery context + * @return 0 on success + * @return Non-zero on error + */ +int delivery_index_conda_artifacts(struct Delivery *ctx); + +/** + * Execute Delivery test array + * @param ctx pointer to Delivery context + */ +void delivery_tests_run(struct Delivery *ctx); + +/** + * Determine which packages are to be installed directly from conda or pip, + * and which packages need to be built locally + * @param ctx pointer to Delivery context + * @param type DEFER_CONDA (filter conda packages) + * @param type DEFER_PIP (filter python packages) + */ +void delivery_defer_packages(struct Delivery *ctx, int type); + +/** + * Configure and activate a Conda installation based on Delivery context + * @param ctx pointer to Delivery context + * @param conda_install_dir path to Conda installation + */ +void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir); + +/** + * Install Conda + * @param install_script path to Conda installation script + * @param conda_install_dir path to install Conda + */ +void delivery_install_conda(char *install_script, char *conda_install_dir); + +/** + * Generate a formatted release string + * + * Formatters: + * + * - `%%n` = Delivery Name + * - `%%c` = Delivery Codename (HST mission, only) + * - `%%m` = Mission + * - `%%R` = Delivery Revision number (or "final") + * - `%%r` = Delivery Revision number + * - `%%v` = Delivery Version + * - `%%P` = Python version (i.e. 3.9.1) + * - `%%p` = Compact Python version (i.e. 3.9.1 -> 39) + * - `%%a` = System architecture name + * - `%%o` = System platform name + * - `%%t` = Delivery timestamp (Unix Epoch) + * + * @param ctx pointer to Delivery context + * @param dest NULL pointer to string, or initialized string + * @param fmt release format string + * @return 0 on success, -1 on error + */ +int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt); + +// helper function +void delivery_gather_tool_versions(struct Delivery *ctx); + +// helper function +int delivery_init_tmpdir(struct Delivery *ctx); + +int delivery_init_artifactory(struct Delivery *ctx); + +int delivery_artifact_upload(struct Delivery *ctx); + +int delivery_mission_render_files(struct Delivery *ctx); + +int delivery_docker(struct Delivery *ctx); + +int delivery_fixup_test_results(struct Delivery *ctx); + +int *bootstrap_build_info(struct Delivery *ctx); + +int delivery_dump_metadata(struct Delivery *ctx); + +#endif //STASIS_DELIVERY_H diff --git a/include/docker.h b/include/docker.h index a5fdbc4..ff8a8d5 100644 --- a/include/docker.h +++ b/include/docker.h @@ -1,17 +1,17 @@ //! @file docker.h -#ifndef OMC_DOCKER_H -#define OMC_DOCKER_H +#ifndef STASIS_DOCKER_H +#define STASIS_DOCKER_H //! Flag to squelch output from docker_exec() -#define OMC_DOCKER_QUIET 1 << 1 +#define STASIS_DOCKER_QUIET 1 << 1 //! Flag for older style docker build -#define OMC_DOCKER_BUILD 1 << 1 +#define STASIS_DOCKER_BUILD 1 << 1 //! Flag for docker buildx -#define OMC_DOCKER_BUILD_X 1 << 2 +#define STASIS_DOCKER_BUILD_X 1 << 2 //! Compress "docker save"ed images with a compression program -#define OMC_DOCKER_IMAGE_COMPRESSION "zstd" +#define STASIS_DOCKER_IMAGE_COMPRESSION "zstd" struct DockerCapabilities { int podman; //!< Is "docker" really podman? @@ -42,7 +42,7 @@ int docker_capable(struct DockerCapabilities *result); /** * Execute a docker command * - * Use the `OMC_DOCKER_QUIET` flag to suppress all output from stdout and stderr. + * Use the `STASIS_DOCKER_QUIET` flag to suppress all output from stdout and stderr. * * ```c * if (docker_exec("run --rm -t ubuntu:latest /bin/bash -c 'echo Hello world'", 0)) { @@ -87,4 +87,4 @@ void docker_sanitize_tag(char *str); int docker_validate_compression_program(char *prog); -#endif //OMC_DOCKER_H +#endif //STASIS_DOCKER_H diff --git a/include/download.h b/include/download.h index 568809e..058812e 100644 --- a/include/download.h +++ b/include/download.h @@ -1,10 +1,10 @@ //! @file download.h -#ifndef OMC_DOWNLOAD_H -#define OMC_DOWNLOAD_H +#ifndef STASIS_DOWNLOAD_H +#define STASIS_DOWNLOAD_H #include size_t download_writer(void *fp, size_t size, size_t nmemb, void *stream); long download(char *url, const char *filename, char **errmsg); -#endif //OMC_DOWNLOAD_H +#endif //STASIS_DOWNLOAD_H diff --git a/include/environment.h b/include/environment.h index 484f02c..34bc600 100644 --- a/include/environment.h +++ b/include/environment.h @@ -1,8 +1,8 @@ /** * @file environment.h */ -#ifndef OMC_ENVIRONMENT_H -#define OMC_ENVIRONMENT_H +#ifndef STASIS_ENVIRONMENT_H +#define STASIS_ENVIRONMENT_H #include #include @@ -20,4 +20,4 @@ char *runtime_expand_var(RuntimeEnv *env, char *input); void runtime_export(RuntimeEnv *env, char **keys); void runtime_apply(RuntimeEnv *env); void runtime_free(RuntimeEnv *env); -#endif //OMC_ENVIRONMENT_H +#endif //STASIS_ENVIRONMENT_H diff --git a/include/ini.h b/include/ini.h index 022a066..af2639b 100644 --- a/include/ini.h +++ b/include/ini.h @@ -1,7 +1,7 @@ /// @file ini.h -#ifndef OMC_INI_H -#define OMC_INI_H +#ifndef STASIS_INI_H +#define STASIS_INI_H #include #include @@ -217,4 +217,4 @@ int ini_write(struct INIFILE *ini, FILE **stream, unsigned mode); * @param ini */ void ini_free(struct INIFILE **ini); -#endif //OMC_INI_H +#endif //STASIS_INI_H diff --git a/include/junitxml.h b/include/junitxml.h index 2d94eb8..504b8e4 100644 --- a/include/junitxml.h +++ b/include/junitxml.h @@ -1,6 +1,6 @@ /// @file junitxml.h -#ifndef OMC_JUNITXML_H -#define OMC_JUNITXML_H +#ifndef STASIS_JUNITXML_H +#define STASIS_JUNITXML_H #include #define JUNIT_RESULT_STATE_NONE 0 @@ -132,4 +132,4 @@ struct JUNIT_Testsuite *junitxml_testsuite_read(const char *filename); */ void junitxml_testsuite_free(struct JUNIT_Testsuite **testsuite); -#endif //OMC_JUNITXML_H +#endif //STASIS_JUNITXML_H diff --git a/include/omc.h b/include/omc.h deleted file mode 100644 index e728207..0000000 --- a/include/omc.h +++ /dev/null @@ -1,99 +0,0 @@ -//! @file omc.h -#ifndef OMC_OMC_H -#define OMC_OMC_H - -#include -#include -#include -#include -#include -#include -#include - -#define SYSERROR(MSG, ...) do { \ - fprintf(stderr, "%s:%s:%d:%s - ", path_basename(__FILE__), __FUNCTION__, __LINE__, strerror(errno) ? "info" : strerror(errno)); \ - fprintf(stderr, MSG LINE_SEP, __VA_ARGS__); \ -} while (0) -#define OMC_BUFSIZ 8192 -#define OMC_NAME_MAX 255 -#define OMC_DIRSTACK_MAX 1024 -#define OMC_TIME_STR_MAX 128 -#define HTTP_ERROR(X) X >= 400 - -#include "config.h" -#include "template.h" -#include "utils.h" -#include "copy.h" -#include "ini.h" -#include "conda.h" -#include "environment.h" -#include "artifactory.h" -#include "docker.h" -#include "deliverable.h" -#include "str.h" -#include "strlist.h" -#include "system.h" -#include "download.h" -#include "recipe.h" -#include "relocation.h" -#include "wheel.h" -#include "junitxml.h" - -#define guard_runtime_free(X) do { if (X) { runtime_free(X); X = NULL; } } while (0) -#define guard_strlist_free(X) do { if ((*X)) { strlist_free(X); (*X) = NULL; } } while (0) -#define guard_free(X) do { if (X) { free(X); X = NULL; } } while (0) -#define GENERIC_ARRAY_FREE(ARR) do { \ - for (size_t ARR_I = 0; ARR && ARR[ARR_I] != NULL; ARR_I++) { \ - guard_free(ARR[ARR_I]); \ - } \ - guard_free(ARR); \ -} while (0) - -#define COE_CHECK_ABORT(COND, MSG) \ - do {\ - if (!globals.continue_on_error && COND) { \ - msg(OMC_MSG_ERROR, MSG ": Aborting execution (--continue-on-error/-C is not enabled)\n"); \ - exit(1); \ - } \ - } while (0) - -struct OMC_GLOBAL { - bool verbose; //!< Enable verbose output - bool always_update_base_environment; //!< Update base environment immediately after activation - bool continue_on_error; //!< Do not stop on test failures - bool conda_fresh_start; //!< Always install a new copy of Conda - bool enable_docker; //!< Enable docker image builds - bool enable_artifactory; //!< Enable artifactory uploads - bool enable_testing; //!< Enable package testing - struct StrList *conda_packages; //!< Conda packages to install after initial activation - struct StrList *pip_packages; //!< Pip packages to install after initial activation - char *tmpdir; //!< Path to temporary storage directory - char *conda_install_prefix; //!< Path to install conda - char *sysconfdir; //!< Path where OMC reads its configuration files (mission directory, etc) - struct { - char *tox_posargs; - } workaround; - struct Jfrog { - char *jfrog_artifactory_base_url; - char *jfrog_artifactory_product; - char *cli_major_ver; - char *version; - char *os; - char *arch; - char *remote_filename; - char *repo; - char *url; - } jfrog; -}; -extern struct OMC_GLOBAL globals; -extern const char *VERSION; -extern const char *AUTHOR; -extern const char *BANNER; - - -/** - * Free memory allocated in global configuration structure - */ -void globals_free(); - -#endif //OMC_OMC_H diff --git a/include/os_darwin.h b/include/os_darwin.h index 390968c..e8513ff 100644 --- a/include/os_darwin.h +++ b/include/os_darwin.h @@ -1,5 +1,5 @@ -#ifndef OMC_OS_DARWIN_H -#define OMC_OS_DARWIN_H +#ifndef STASIS_OS_DARWIN_H +#define STASIS_OS_DARWIN_H #include diff --git a/include/os_linux.h b/include/os_linux.h index 8c3aed7..d418090 100644 --- a/include/os_linux.h +++ b/include/os_linux.h @@ -1,5 +1,5 @@ -#ifndef OMC_OS_LINUX_H -#define OMC_OS_LINUX_H +#ifndef STASIS_OS_LINUX_H +#define STASIS_OS_LINUX_H #include diff --git a/include/recipe.h b/include/recipe.h index f1b4df1..4dea248 100644 --- a/include/recipe.h +++ b/include/recipe.h @@ -1,6 +1,6 @@ //! @file recipe.h -#ifndef OMC_RECIPE_H -#define OMC_RECIPE_H +#ifndef STASIS_RECIPE_H +#define STASIS_RECIPE_H #include "str.h" #include "utils.h" @@ -69,4 +69,4 @@ int recipe_clone(char *recipe_dir, char *url, char *gitref, char **result); */ int recipe_get_type(char *repopath); -#endif //OMC_RECIPE_H +#endif //STASIS_RECIPE_H diff --git a/include/relocation.h b/include/relocation.h index 924f00f..9a1f0f4 100644 --- a/include/relocation.h +++ b/include/relocation.h @@ -1,15 +1,15 @@ /** * @file relocation.h */ -#ifndef OMC_RELOCATION_H -#define OMC_RELOCATION_H +#ifndef STASIS_RELOCATION_H +#define STASIS_RELOCATION_H #include "config.h" #include #include #include -#if defined(OMC_OS_DARWIN) +#if defined(STASIS_OS_DARWIN) #include # else #include @@ -21,4 +21,4 @@ int replace_text(char *original, const char *target, const char *replacement, unsigned flags); int file_replace_text(const char* filename, const char* target, const char* replacement, unsigned flags); -#endif //OMC_RELOCATION_H +#endif //STASIS_RELOCATION_H diff --git a/include/rules.h b/include/rules.h index 63eade7..666d331 100644 --- a/include/rules.h +++ b/include/rules.h @@ -2,10 +2,10 @@ // Created by jhunk on 12/18/23. // -#ifndef OMC_RULES_H -#define OMC_RULES_H +#ifndef STASIS_RULES_H +#define STASIS_RULES_H -#include "omc.h" +#include "core.h" -#endif //OMC_RULES_H +#endif //STASIS_RULES_H diff --git a/include/str.h b/include/str.h index 595a055..4cf221d 100644 --- a/include/str.h +++ b/include/str.h @@ -1,20 +1,20 @@ /** * @file str.h */ -#ifndef OMC_STR_H -#define OMC_STR_H +#ifndef STASIS_STR_H +#define STASIS_STR_H #include #include #include #include #include -#include "omc.h" +#include "core.h" -#define OMC_SORT_ALPHA 1 << 0 -#define OMC_SORT_NUMERIC 1 << 1 -#define OMC_SORT_LEN_ASCENDING 1 << 2 -#define OMC_SORT_LEN_DESCENDING 1 << 3 +#define STASIS_SORT_ALPHA 1 << 0 +#define STASIS_SORT_NUMERIC 1 << 1 +#define STASIS_SORT_LEN_ASCENDING 1 << 2 +#define STASIS_SORT_LEN_DESCENDING 1 << 3 /** * Determine how many times the character `ch` appears in `sptr` string @@ -115,10 +115,10 @@ char *substring_between(char *sptr, const char *delims); * Sort an array of strings * @param arr a NULL terminated array of strings * @param sort_mode - * - OMC_SORT_LEN_DESCENDING - * - OMC_SORT_LEN_ASCENDING - * - OMC_SORT_ALPHA - * - OMC_SORT_NUMERIC + * - STASIS_SORT_LEN_DESCENDING + * - STASIS_SORT_LEN_ASCENDING + * - STASIS_SORT_ALPHA + * - STASIS_SORT_NUMERIC */ void strsort(char **arr, unsigned int sort_mode); @@ -307,4 +307,4 @@ char *tolower_s(char *s); */ char *to_short_version(const char *s); -#endif //OMC_STR_H +#endif //STASIS_STR_H diff --git a/include/strlist.h b/include/strlist.h index 3f35e23..dd22a0a 100644 --- a/include/strlist.h +++ b/include/strlist.h @@ -2,8 +2,8 @@ * String array convenience functions * @file strlist.h */ -#ifndef OMC_STRLIST_H -#define OMC_STRLIST_H +#ifndef STASIS_STRLIST_H +#define STASIS_STRLIST_H #include #include "utils.h" #include "str.h" @@ -52,4 +52,4 @@ extern int strlist_errno; const char *strlist_get_error(int flag); -#endif //OMC_STRLIST_H +#endif //STASIS_STRLIST_H diff --git a/include/system.h b/include/system.h index 94d5a36..7019b92 100644 --- a/include/system.h +++ b/include/system.h @@ -2,8 +2,8 @@ * System functions * @file system.h */ -#ifndef OMC_SYSTEM_H -#define OMC_SYSTEM_H +#ifndef STASIS_SYSTEM_H +#define STASIS_SYSTEM_H #include #include @@ -14,7 +14,7 @@ #include #include -#define OMC_SHELL_SAFE_RESTRICT ";&|()" +#define STASIS_SHELL_SAFE_RESTRICT ";&|()" struct Process { // Write stdout stream to file @@ -31,4 +31,4 @@ int shell(struct Process *proc, char *args); int shell_safe(struct Process *proc, char *args); char *shell_output(const char *command, int *status); -#endif //OMC_SYSTEM_H +#endif //STASIS_SYSTEM_H diff --git a/include/template.h b/include/template.h index 362eb3d..ba62667 100644 --- a/include/template.h +++ b/include/template.h @@ -1,8 +1,8 @@ //! @file template.h -#ifndef OMC_TEMPLATE_H -#define OMC_TEMPLATE_H +#ifndef STASIS_TEMPLATE_H +#define STASIS_TEMPLATE_H -#include "omc.h" +#include "core.h" /** * Map a text value to a pointer in memory @@ -64,4 +64,4 @@ struct tplfunc_frame { }; void tpl_register_func(char *key, struct tplfunc_frame *frame); -#endif //OMC_TEMPLATE_H +#endif //STASIS_TEMPLATE_H diff --git a/include/utils.h b/include/utils.h index 8840a0d..2c80e77 100644 --- a/include/utils.h +++ b/include/utils.h @@ -1,6 +1,6 @@ //! @file utils.h -#ifndef OMC_UTILS_H -#define OMC_UTILS_H +#ifndef STASIS_UTILS_H +#define STASIS_UTILS_H #include #include #include @@ -10,7 +10,7 @@ #include #include "system.h" -#if defined(OMC_OS_WINDOWS) +#if defined(STASIS_OS_WINDOWS) #define PATH_ENV_VAR "path" #define DIR_SEP "\\" #define PATH_SEP ";" @@ -22,8 +22,8 @@ #define LINE_SEP "\n" #endif -#define OMC_XML_PRETTY_PRINT_PROG "xmllint" -#define OMC_XML_PRETTY_PRINT_ARGS "--format" +#define STASIS_XML_PRETTY_PRINT_PROG "xmllint" +#define STASIS_XML_PRETTY_PRINT_ARGS "--format" typedef int (ReaderFn)(size_t line, char **); @@ -167,7 +167,7 @@ char *git_describe(const char *path); char *git_rev_parse(const char *path, char *args); /** - * Helper function to initialize simple OMC internal path strings + * Helper function to initialize simple STASIS internal path strings * * ```c * char *mypath = NULL; @@ -188,43 +188,43 @@ char *git_rev_parse(const char *path, char *args); */ int path_store(char **destptr, size_t maxlen, const char *base, const char *path); -#if defined(OMC_DUMB_TERMINAL) -#define OMC_COLOR_RED "" -#define OMC_COLOR_GREEN "" -#define OMC_COLOR_YELLOW "" -#define OMC_COLOR_BLUE "" -#define OMC_COLOR_WHITE "" -#define OMC_COLOR_RESET "" +#if defined(STASIS_DUMB_TERMINAL) +#define STASIS_COLOR_RED "" +#define STASIS_COLOR_GREEN "" +#define STASIS_COLOR_YELLOW "" +#define STASIS_COLOR_BLUE "" +#define STASIS_COLOR_WHITE "" +#define STASIS_COLOR_RESET "" #else //! Set output color to red -#define OMC_COLOR_RED "\e[1;91m" +#define STASIS_COLOR_RED "\e[1;91m" //! Set output color to green -#define OMC_COLOR_GREEN "\e[1;92m" +#define STASIS_COLOR_GREEN "\e[1;92m" //! Set output color to yellow -#define OMC_COLOR_YELLOW "\e[1;93m" +#define STASIS_COLOR_YELLOW "\e[1;93m" //! Set output color to blue -#define OMC_COLOR_BLUE "\e[1;94m" +#define STASIS_COLOR_BLUE "\e[1;94m" //! Set output color to white -#define OMC_COLOR_WHITE "\e[1;97m" +#define STASIS_COLOR_WHITE "\e[1;97m" //! Reset output color to terminal default -#define OMC_COLOR_RESET "\e[0;37m\e[0m" +#define STASIS_COLOR_RESET "\e[0;37m\e[0m" #endif -#define OMC_MSG_SUCCESS 0 +#define STASIS_MSG_SUCCESS 0 //! Suppress printing of the message text -#define OMC_MSG_NOP 1 << 0 +#define STASIS_MSG_NOP 1 << 0 //! The message is an error -#define OMC_MSG_ERROR 1 << 1 +#define STASIS_MSG_ERROR 1 << 1 //! The message is a warning -#define OMC_MSG_WARN 1 << 2 +#define STASIS_MSG_WARN 1 << 2 //! The message will be indented once -#define OMC_MSG_L1 1 << 3 +#define STASIS_MSG_L1 1 << 3 //! The message will be indented twice -#define OMC_MSG_L2 1 << 4 +#define STASIS_MSG_L2 1 << 4 //! The message will be indented thrice -#define OMC_MSG_L3 1 << 5 +#define STASIS_MSG_L3 1 << 5 //! The message will only be printed in verbose mode -#define OMC_MSG_RESTRICT 1 << 6 +#define STASIS_MSG_RESTRICT 1 << 6 void msg(unsigned type, char *fmt, ...); @@ -278,7 +278,7 @@ int isempty_dir(const char *path); int xml_pretty_print_in_place(const char *filename, const char *pretty_print_prog, const char *pretty_print_args); /** - * Applies OMC fixups to a tox ini config + * Applies STASIS fixups to a tox ini config * @param filename path to tox.ini * @param result path to processed configuration * @return 0 on success, -1 on error @@ -351,4 +351,4 @@ int redact_sensitive(const char **to_redact, size_t to_redact_size, char *src, c */ struct StrList *listdir(const char *path); -#endif //OMC_UTILS_H +#endif //STASIS_UTILS_H diff --git a/include/wheel.h b/include/wheel.h index cca15d4..94cf46a 100644 --- a/include/wheel.h +++ b/include/wheel.h @@ -1,5 +1,5 @@ -#ifndef OMC_WHEEL_H -#define OMC_WHEEL_H +#ifndef STASIS_WHEEL_H +#define STASIS_WHEEL_H #include #include @@ -18,4 +18,4 @@ struct Wheel { }; struct Wheel *get_wheel_file(const char *basepath, const char *name, char *to_match[]); -#endif //OMC_WHEEL_H +#endif //STASIS_WHEEL_H diff --git a/omc.ini b/omc.ini deleted file mode 100644 index bf6930b..0000000 --- a/omc.ini +++ /dev/null @@ -1,36 +0,0 @@ -; (bool) Keep going even if a test fails -continue_on_error = false - -; (bool) Update all packages in the base to the latest release -always_update_base_environment = false - -; (bool) Remove conda installation during initialization -; true = reinstall conda -; false = do not reinstall conda -conda_fresh_start = true - -; (string) Install conda in a custom prefix -; DEFAULT: Conda will be installed under omc/conda -; NOTE: conda_fresh_start will automatically be set to "false" -;conda_install_prefix = /path/to/conda - -; (list) Conda packages to be installed/overridden in the base environment -conda_packages = - conda>=23.7.0 - conda-build>=3.22.0 - boa - conda-verify - conda-libmamba-solver - -; (list) Python packages to be installed/overridden in the base environment -;pip_packages = - -[jfrog_cli_download] -url = https://releases.jfrog.io/artifactory -product = jfrog-cli -version_series = v2-jf -version = [RELEASE] -filename = jf - -[deploy:artifactory] - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f075102..c42bb0f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ include_directories(${CMAKE_BINARY_DIR}/include) include_directories(${CMAKE_SOURCE_DIR}/include) include_directories(${PROJECT_BINARY_DIR}) -add_library(ohmycal STATIC +add_library(stasis_core STATIC globals.c str.c strlist.c @@ -12,7 +12,7 @@ add_library(ohmycal STATIC utils.c system.c download.c - deliverable.c + delivery.c recipe.c relocation.c wheel.c @@ -24,13 +24,13 @@ add_library(ohmycal STATIC junitxml.c ) -add_executable(omc - omc_main.c +add_executable(stasis + stasis_main.c ) -target_link_libraries(omc PRIVATE ohmycal) -target_link_libraries(omc PUBLIC LibXml2::LibXml2) -add_executable(omc_indexer - omc_indexer.c +target_link_libraries(stasis PRIVATE stasis_core) +target_link_libraries(stasis PUBLIC LibXml2::LibXml2) +add_executable(stasis_indexer + stasis_indexer.c ) -target_link_libraries(omc_indexer PRIVATE ohmycal) -install(TARGETS omc omc_indexer RUNTIME) +target_link_libraries(stasis_indexer PRIVATE stasis_core) +install(TARGETS stasis stasis_indexer RUNTIME) diff --git a/src/artifactory.c b/src/artifactory.c index 5678d64..bd9aa99 100644 --- a/src/artifactory.c +++ b/src/artifactory.c @@ -1,6 +1,6 @@ -#include "omc.h" +#include "core.h" -extern struct OMC_GLOBAL globals; +extern struct STASIS_GLOBAL globals; int artifactory_download_cli(char *dest, char *jfrog_artifactory_base_url, @@ -12,8 +12,8 @@ int artifactory_download_cli(char *dest, char *remote_filename) { char url[PATH_MAX] = {0}; char path[PATH_MAX] = {0}; - char os_ident[OMC_NAME_MAX] = {0}; - char arch_ident[OMC_NAME_MAX] = {0}; + char os_ident[STASIS_NAME_MAX] = {0}; + char arch_ident[STASIS_NAME_MAX] = {0}; // convert platform string to lower-case strcpy(os_ident, os); @@ -73,7 +73,7 @@ int artifactory_download_cli(char *dest, } void jfrt_register_opt_str(char *jfrt_val, const char *opt_name, struct StrList **opt_map) { - char data[OMC_BUFSIZ]; + char data[STASIS_BUFSIZ]; memset(data, 0, sizeof(data)); if (jfrt_val == NULL) { @@ -85,7 +85,7 @@ void jfrt_register_opt_str(char *jfrt_val, const char *opt_name, struct StrList } void jfrt_register_opt_bool(bool jfrt_val, const char *opt_name, struct StrList **opt_map) { - char data[OMC_BUFSIZ]; + char data[STASIS_BUFSIZ]; memset(data, 0, sizeof(data)); if (jfrt_val == false) { @@ -97,7 +97,7 @@ void jfrt_register_opt_bool(bool jfrt_val, const char *opt_name, struct StrList } void jfrt_register_opt_int(int jfrt_val, const char *opt_name, struct StrList **opt_map) { - char data[OMC_BUFSIZ]; + char data[STASIS_BUFSIZ]; memset(data, 0, sizeof(data)); if (jfrt_val == 0) { @@ -109,7 +109,7 @@ void jfrt_register_opt_int(int jfrt_val, const char *opt_name, struct StrList ** } void jfrt_register_opt_long(long jfrt_val, const char *opt_name, struct StrList **opt_map) { - char data[OMC_BUFSIZ]; + char data[STASIS_BUFSIZ]; memset(data, 0, sizeof(data)); if (jfrt_val == 0) { @@ -141,18 +141,18 @@ static int auth_required(char *cmd) { } int jfrt_auth_init(struct JFRT_Auth *auth_ctx) { - char *url = getenv("OMC_JF_ARTIFACTORY_URL"); - char *user = getenv("OMC_JF_USER"); - char *access_token = getenv("OMC_JF_ACCESS_TOKEN"); - char *password = getenv("OMC_JF_PASSWORD"); - char *ssh_key_path = getenv("OMC_JF_SSH_KEY_PATH"); - char *ssh_passphrase = getenv("OMC_JF_SSH_PASSPHRASE"); - char *client_cert_key_path = getenv("OMC_JF_CLIENT_CERT_KEY_PATH"); - char *client_cert_path = getenv("OMC_JF_CLIENT_CERT_PATH"); + char *url = getenv("STASIS_JF_ARTIFACTORY_URL"); + char *user = getenv("STASIS_JF_USER"); + char *access_token = getenv("STASIS_JF_ACCESS_TOKEN"); + char *password = getenv("STASIS_JF_PASSWORD"); + char *ssh_key_path = getenv("STASIS_JF_SSH_KEY_PATH"); + char *ssh_passphrase = getenv("STASIS_JF_SSH_PASSPHRASE"); + char *client_cert_key_path = getenv("STASIS_JF_CLIENT_CERT_KEY_PATH"); + char *client_cert_path = getenv("STASIS_JF_CLIENT_CERT_PATH"); if (!url) { fprintf(stderr, "Artifactory URL is not configured:\n"); - fprintf(stderr, "please set OMC_JF_ARTIFACTORY_URL\n"); + fprintf(stderr, "please set STASIS_JF_ARTIFACTORY_URL\n"); return -1; } auth_ctx->url = url; @@ -184,10 +184,10 @@ int jfrt_auth_init(struct JFRT_Auth *auth_ctx) { auth_ctx->client_cert_path = client_cert_path; } else { fprintf(stderr, "Artifactory authentication is not configured:\n"); - fprintf(stderr, "set OMC_JF_USER and OMC_JF_PASSWORD\n"); - fprintf(stderr, "or, set OMC_JF_ACCESS_TOKEN\n"); - fprintf(stderr, "or, set OMC_JF_SSH_KEY_PATH and OMC_JF_SSH_KEY_PASSPHRASE\n"); - fprintf(stderr, "or, set OMC_JF_CLIENT_CERT_KEY_PATH and OMC_JF_CLIENT_CERT_PATH\n"); + fprintf(stderr, "set STASIS_JF_USER and STASIS_JF_PASSWORD\n"); + fprintf(stderr, "or, set STASIS_JF_ACCESS_TOKEN\n"); + fprintf(stderr, "or, set STASIS_JF_SSH_KEY_PATH and STASIS_JF_SSH_KEY_PASSPHRASE\n"); + fprintf(stderr, "or, set STASIS_JF_CLIENT_CERT_KEY_PATH and STASIS_JF_CLIENT_CERT_PATH\n"); return -1; } return 0; @@ -195,8 +195,8 @@ int jfrt_auth_init(struct JFRT_Auth *auth_ctx) { int jfrog_cli(struct JFRT_Auth *auth, char *args) { struct Process proc; - char cmd[OMC_BUFSIZ]; - char cmd_redacted[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; + char cmd_redacted[STASIS_BUFSIZ]; int status; memset(&proc, 0, sizeof(proc)); @@ -244,7 +244,7 @@ int jfrog_cli(struct JFRT_Auth *auth, char *args) { // Pings are noisy. Squelch them. if (!strstr(args, "rt ping")) { - msg(OMC_MSG_L2, "Executing: %s\n", cmd_redacted); + msg(STASIS_MSG_L2, "Executing: %s\n", cmd_redacted); } if (!globals.verbose) { @@ -256,28 +256,28 @@ int jfrog_cli(struct JFRT_Auth *auth, char *args) { } int jfrog_cli_rt(struct JFRT_Auth *auth, char *args) { - char cmd[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd) - 1, "rt %s", args); return jfrog_cli(auth, args); } int jfrog_cli_rt_build_collect_env(struct JFRT_Auth *auth, char *build_name, char *build_number) { - char cmd[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd) - 1, "rt build-collect-env \"%s\" \"%s\"", build_name, build_number); return jfrog_cli(auth, cmd); } int jfrog_cli_rt_build_publish(struct JFRT_Auth *auth, char *build_name, char *build_number) { - char cmd[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd) - 1, "rt build-publish \"%s\" \"%s\"", build_name, build_number); return jfrog_cli(auth, cmd); } int jfrog_cli_rt_ping(struct JFRT_Auth *auth) { - char cmd[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd) - 1, "rt ping"); @@ -285,7 +285,7 @@ int jfrog_cli_rt_ping(struct JFRT_Auth *auth) { } int jfrog_cli_rt_download(struct JFRT_Auth *auth, struct JFRT_Download *ctx, char *repo_path, char *dest) { - char cmd[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); if (isempty(repo_path)) { @@ -352,7 +352,7 @@ int jfrog_cli_rt_download(struct JFRT_Auth *auth, struct JFRT_Download *ctx, cha } int jfrog_cli_rt_upload(struct JFRT_Auth *auth, struct JFRT_Upload *ctx, char *src, char *repo_path) { - char cmd[OMC_BUFSIZ]; + char cmd[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); if (isempty(src)) { diff --git a/src/conda.c b/src/conda.c index efb42fa..342b6af 100644 --- a/src/conda.c +++ b/src/conda.c @@ -5,13 +5,11 @@ #include #include "conda.h" -extern struct OMC_GLOBAL globals; - int python_exec(const char *args) { char command[PATH_MAX]; memset(command, 0, sizeof(command)); snprintf(command, sizeof(command) - 1, "python %s", args); - msg(OMC_MSG_L3, "Executing: %s\n", command); + msg(STASIS_MSG_L3, "Executing: %s\n", command); return system(command); } @@ -19,7 +17,7 @@ int pip_exec(const char *args) { char command[PATH_MAX]; memset(command, 0, sizeof(command)); snprintf(command, sizeof(command) - 1, "python -m pip %s", args); - msg(OMC_MSG_L3, "Executing: %s\n", command); + msg(STASIS_MSG_L3, "Executing: %s\n", command); return system(command); } @@ -51,7 +49,7 @@ int conda_exec(const char *args) { } snprintf(command, sizeof(command) - 1, "%s %s", conda_as, args); - msg(OMC_MSG_L3, "Executing: %s\n", command); + msg(STASIS_MSG_L3, "Executing: %s\n", command); return system(command); } @@ -109,7 +107,7 @@ int conda_activate(const char *root, const char *env_name) { // Parse the log file: // 1. Extract the environment keys and values from the sub-shell - // 2. Apply it to OMC's runtime environment + // 2. Apply it to STASIS's runtime environment // 3. Now we're ready to execute conda commands anywhere fp = fopen(proc.f_stdout, "r"); if (!fp) { @@ -118,7 +116,7 @@ int conda_activate(const char *root, const char *env_name) { } int i = 0; while (!feof(fp)) { - char buf[OMC_BUFSIZ] = {0}; + char buf[STASIS_BUFSIZ] = {0}; int ch = 0; size_t z = 0; // We are ingesting output from "env -0", can't use fgets() @@ -142,10 +140,10 @@ int conda_activate(const char *root, const char *env_name) { return -1; } if (!part[0]) { - msg(OMC_MSG_WARN | OMC_MSG_L1, "Invalid environment variable key ignored: '%s'\n", buf); + msg(STASIS_MSG_WARN | STASIS_MSG_L1, "Invalid environment variable key ignored: '%s'\n", buf); i++; } else if (!part[1]) { - msg(OMC_MSG_WARN | OMC_MSG_L1, "Invalid environment variable value ignored: '%s'\n", buf); + msg(STASIS_MSG_WARN | STASIS_MSG_L1, "Invalid environment variable value ignored: '%s'\n", buf); i++; } else { setenv(part[0], part[1], 1); @@ -193,7 +191,7 @@ int conda_check_required() { guard_free(cmd_out); guard_strlist_free(&result); } else { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "The base package requirement check could not be performed\n"); + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "The base package requirement check could not be performed\n"); return 2; } return 0; @@ -234,7 +232,7 @@ void conda_setup_headless() { } if (conda_exec(cmd)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Unable to install user-defined base packages (conda)\n"); + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Unable to install user-defined base packages (conda)\n"); exit(1); } } @@ -256,13 +254,13 @@ void conda_setup_headless() { } if (pip_exec(cmd)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Unable to install user-defined base packages (pip)\n"); + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Unable to install user-defined base packages (pip)\n"); exit(1); } } if (conda_check_required()) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Your OMC configuration lacks the bare" + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Your STASIS configuration lacks the bare" " minimum software required to build conda packages." " Please fix it.\n"); exit(1); diff --git a/src/copy.c b/src/copy.c index dcfd924..323208b 100644 --- a/src/copy.c +++ b/src/copy.c @@ -3,7 +3,7 @@ int copy2(const char *src, const char *dest, unsigned int op) { size_t bytes_read; size_t bytes_written; - char buf[OMC_BUFSIZ]; + char buf[STASIS_BUFSIZ]; struct stat src_stat, dnamest; FILE *fp1, *fp2; diff --git a/src/deliverable.c b/src/deliverable.c deleted file mode 100644 index f72f535..0000000 --- a/src/deliverable.c +++ /dev/null @@ -1,2100 +0,0 @@ -#define _GNU_SOURCE - -#include -#include "omc.h" - -extern struct OMC_GLOBAL globals; - -static void ini_has_key_required(struct INIFILE *ini, const char *section_name, char *key) { - int status = ini_has_key(ini, section_name, key); - if (!status) { - SYSERROR("%s:%s key is required but not defined", section_name, key); - exit(1); - } -} - -static void ini_getval_required(struct INIFILE *ini, char *section_name, char *key, unsigned type, union INIVal *val) { - int status = ini_getval(ini, section_name, key, type, val); - if (status || isempty(val->as_char_p)) { - SYSERROR("%s:%s value is required but not defined", section_name, key); - exit(1); - } -} - -static void conv_int(int *x, union INIVal val) { - *x = val.as_int; -} - -static void conv_str(char **x, union INIVal val) { - if (*x) { - guard_free(*x); - } - if (val.as_char_p) { - char *tplop = tpl_render(val.as_char_p); - if (tplop) { - *x = tplop; - } else { - *x = NULL; - } - } else { - *x = NULL; - } -} - -static void conv_str_noexpand(char **x, union INIVal val) { - if (*x) { - guard_free(*x); - } - *x = strdup(val.as_char_p); -} - -static void conv_strlist(struct StrList **x, char *tok, union INIVal val) { - if (!(*x)) - (*x) = strlist_init(); - if (val.as_char_p) { - char *tplop = tpl_render(val.as_char_p); - if (tplop) { - strip(tplop); - strlist_append_tokenize((*x), tplop, tok); - guard_free(tplop); - } - } -} - -static void conv_bool(bool *x, union INIVal val) { - *x = val.as_bool; -} - -int delivery_init_tmpdir(struct Delivery *ctx) { - char *tmpdir = NULL; - char *x = NULL; - int unusable = 0; - errno = 0; - - x = getenv("TMPDIR"); - if (x) { - guard_free(ctx->storage.tmpdir); - tmpdir = strdup(x); - } else { - tmpdir = ctx->storage.tmpdir; - } - - if (!tmpdir) { - // memory error - return -1; - } - - // If the directory doesn't exist, create it - if (access(tmpdir, F_OK) < 0) { - if (mkdirs(tmpdir, 0755) < 0) { - msg(OMC_MSG_ERROR | OMC_MSG_L1, "Unable to create temporary storage directory: %s (%s)\n", 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(OMC_MSG_ERROR | OMC_MSG_L1, "%s requires at least 0755 permissions.\n"); - goto l_delivery_init_tmpdir_fatal; - } - - struct statvfs st; - if (statvfs(tmpdir, &st) < 0) { - goto l_delivery_init_tmpdir_fatal; - } - -#if defined(OMC_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(OMC_MSG_ERROR | OMC_MSG_L1, "%s is mounted with noexec\n", tmpdir); - goto l_delivery_init_tmpdir_fatal; - } -#endif - if ((st.f_flag & ST_RDONLY) != 0) { - msg(OMC_MSG_ERROR | OMC_MSG_L1, "%s is mounted read-only\n", tmpdir); - goto l_delivery_init_tmpdir_fatal; - } - - if (!globals.tmpdir) { - globals.tmpdir = strdup(tmpdir); - } - - if (!ctx->storage.tmpdir) { - ctx->storage.tmpdir = strdup(globals.tmpdir); - } - return unusable; - - l_delivery_init_tmpdir_fatal: - unusable = 1; - return unusable; -} - -void delivery_free(struct Delivery *ctx) { - guard_free(ctx->system.arch); - GENERIC_ARRAY_FREE(ctx->system.platform); - guard_free(ctx->meta.name); - guard_free(ctx->meta.version); - guard_free(ctx->meta.codename); - guard_free(ctx->meta.mission); - guard_free(ctx->meta.python); - guard_free(ctx->meta.mission); - guard_free(ctx->meta.python_compact); - guard_free(ctx->meta.based_on); - guard_runtime_free(ctx->runtime.environ); - guard_free(ctx->storage.root); - guard_free(ctx->storage.tmpdir); - guard_free(ctx->storage.delivery_dir); - guard_free(ctx->storage.tools_dir); - guard_free(ctx->storage.package_dir); - guard_free(ctx->storage.results_dir); - guard_free(ctx->storage.output_dir); - guard_free(ctx->storage.conda_install_prefix); - guard_free(ctx->storage.conda_artifact_dir); - guard_free(ctx->storage.conda_staging_dir); - guard_free(ctx->storage.conda_staging_url); - guard_free(ctx->storage.wheel_artifact_dir); - guard_free(ctx->storage.wheel_staging_dir); - guard_free(ctx->storage.wheel_staging_url); - guard_free(ctx->storage.build_dir); - guard_free(ctx->storage.build_recipes_dir); - guard_free(ctx->storage.build_sources_dir); - guard_free(ctx->storage.build_testing_dir); - guard_free(ctx->storage.build_docker_dir); - guard_free(ctx->storage.mission_dir); - guard_free(ctx->storage.docker_artifact_dir); - guard_free(ctx->info.time_str_epoch); - guard_free(ctx->info.build_name); - guard_free(ctx->info.build_number); - guard_free(ctx->info.release_name); - guard_free(ctx->conda.installer_baseurl); - guard_free(ctx->conda.installer_name); - guard_free(ctx->conda.installer_version); - guard_free(ctx->conda.installer_platform); - guard_free(ctx->conda.installer_arch); - guard_free(ctx->conda.installer_path); - guard_free(ctx->conda.tool_version); - guard_free(ctx->conda.tool_build_version); - guard_strlist_free(&ctx->conda.conda_packages); - guard_strlist_free(&ctx->conda.conda_packages_defer); - guard_strlist_free(&ctx->conda.pip_packages); - guard_strlist_free(&ctx->conda.pip_packages_defer); - 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_free(ctx->tests[i].script); - guard_free(ctx->tests[i].build_recipe); - // test-specific runtime variables - guard_runtime_free(ctx->tests[i].runtime.environ); - } - - guard_free(ctx->rules.release_fmt); - guard_free(ctx->rules.build_name_fmt); - guard_free(ctx->rules.build_number_fmt); - - guard_free(ctx->deploy.docker.test_script); - guard_free(ctx->deploy.docker.registry); - guard_free(ctx->deploy.docker.image_compression); - guard_strlist_free(&ctx->deploy.docker.tags); - guard_strlist_free(&ctx->deploy.docker.build_args); - - for (size_t i = 0; i < sizeof(ctx->deploy.jfrog) / sizeof(ctx->deploy.jfrog[0]); i++) { - guard_free(ctx->deploy.jfrog[i].repo); - guard_free(ctx->deploy.jfrog[i].dest); - guard_strlist_free(&ctx->deploy.jfrog[i].files); - } - - if (ctx->_omc_ini_fp.delivery) { - ini_free(&ctx->_omc_ini_fp.delivery); - } - guard_free(ctx->_omc_ini_fp.delivery_path); - - if (ctx->_omc_ini_fp.cfg) { - // optional extras - ini_free(&ctx->_omc_ini_fp.cfg); - } - guard_free(ctx->_omc_ini_fp.cfg_path); - - if (ctx->_omc_ini_fp.mission) { - ini_free(&ctx->_omc_ini_fp.mission); - } - guard_free(ctx->_omc_ini_fp.mission_path); -} - -void delivery_init_dirs_stage2(struct Delivery *ctx) { - path_store(&ctx->storage.build_recipes_dir, PATH_MAX, ctx->storage.build_dir, "recipes"); - path_store(&ctx->storage.build_sources_dir, PATH_MAX, ctx->storage.build_dir, "sources"); - path_store(&ctx->storage.build_testing_dir, PATH_MAX, ctx->storage.build_dir, "testing"); - path_store(&ctx->storage.build_docker_dir, PATH_MAX, ctx->storage.build_dir, "docker"); - - path_store(&ctx->storage.delivery_dir, PATH_MAX, ctx->storage.output_dir, "delivery"); - path_store(&ctx->storage.results_dir, PATH_MAX, ctx->storage.output_dir, "results"); - path_store(&ctx->storage.package_dir, PATH_MAX, ctx->storage.output_dir, "packages"); - path_store(&ctx->storage.cfgdump_dir, PATH_MAX, ctx->storage.output_dir, "config"); - path_store(&ctx->storage.meta_dir, PATH_MAX, ctx->storage.output_dir, "meta"); - - path_store(&ctx->storage.conda_artifact_dir, PATH_MAX, ctx->storage.package_dir, "conda"); - path_store(&ctx->storage.wheel_artifact_dir, PATH_MAX, ctx->storage.package_dir, "wheels"); - path_store(&ctx->storage.docker_artifact_dir, PATH_MAX, ctx->storage.package_dir, "docker"); -} - -void delivery_init_dirs_stage1(struct Delivery *ctx) { - char *rootdir = getenv("OMC_ROOT"); - if (rootdir) { - if (isempty(rootdir)) { - fprintf(stderr, "OMC_ROOT is set, but empty. Please assign a file system path to this environment variable.\n"); - exit(1); - } - path_store(&ctx->storage.root, PATH_MAX, rootdir, ctx->info.build_name); - } else { - // use "omc" in current working directory - path_store(&ctx->storage.root, PATH_MAX, "omc", ctx->info.build_name); - } - 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(OMC_MSG_ERROR | OMC_MSG_L1, "Set $TMPDIR to a location other than %s\n", globals.tmpdir); - if (globals.tmpdir) - guard_free(globals.tmpdir); - exit(1); - } - - path_store(&ctx->storage.build_dir, PATH_MAX, ctx->storage.root, "build"); - path_store(&ctx->storage.output_dir, PATH_MAX, ctx->storage.root, "output"); - - if (!ctx->storage.mission_dir) { - path_store(&ctx->storage.mission_dir, PATH_MAX, globals.sysconfdir, "mission"); - } - - if (access(ctx->storage.mission_dir, F_OK)) { - msg(OMC_MSG_L1, "%s: %s\n", ctx->storage.mission_dir, strerror(errno)); - exit(1); - } - - // Override installation prefix using global configuration key - if (globals.conda_install_prefix && strlen(globals.conda_install_prefix)) { - // user wants a specific path - globals.conda_fresh_start = false; - /* - if (mkdirs(globals.conda_install_prefix, 0755)) { - msg(OMC_MSG_ERROR | OMC_MSG_L1, "Unable to create directory: %s: %s\n", - strerror(errno), globals.conda_install_prefix); - exit(1); - } - */ - /* - ctx->storage.conda_install_prefix = realpath(globals.conda_install_prefix, NULL); - if (!ctx->storage.conda_install_prefix) { - msg(OMC_MSG_ERROR | OMC_MSG_L1, "realpath(): Conda installation prefix reassignment failed\n"); - exit(1); - } - ctx->storage.conda_install_prefix = strdup(globals.conda_install_prefix); - */ - path_store(&ctx->storage.conda_install_prefix, PATH_MAX, globals.conda_install_prefix, "conda"); - } else { - // install conda under the OMC tree - path_store(&ctx->storage.conda_install_prefix, PATH_MAX, ctx->storage.tools_dir, "conda"); - } -} - -int delivery_init_platform(struct Delivery *ctx) { - msg(OMC_MSG_L2, "Setting architecture\n"); - char archsuffix[20]; - struct utsname uts; - if (uname(&uts)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "uname() failed: %s\n", 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); - 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])); - } - - ctx->system.arch = strdup(uts.machine); - if (!ctx->system.arch) { - // memory error - return -1; - } - - if (!strcmp(ctx->system.arch, "x86_64")) { - strcpy(archsuffix, "64"); - } else { - strcpy(archsuffix, ctx->system.arch); - } - - msg(OMC_MSG_L2, "Setting platform\n"); - strcpy(ctx->system.platform[DELIVERY_PLATFORM], uts.sysname); - if (!strcmp(ctx->system.platform[DELIVERY_PLATFORM], "Darwin")) { - sprintf(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], "osx-%s", archsuffix); - strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "MacOSX"); - strcpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], "macos"); - } else if (!strcmp(ctx->system.platform[DELIVERY_PLATFORM], "Linux")) { - sprintf(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], "linux-%s", archsuffix); - strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "Linux"); - strcpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], "linux"); - } else { - // Not explicitly supported systems - strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], ctx->system.platform[DELIVERY_PLATFORM]); - strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], ctx->system.platform[DELIVERY_PLATFORM]); - strcpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], ctx->system.platform[DELIVERY_PLATFORM]); - tolower_s(ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); - } - - // Declare some important bits as environment variables - setenv("OMC_ARCH", ctx->system.arch, 1); - setenv("OMC_PLATFORM", ctx->system.platform[DELIVERY_PLATFORM], 1); - setenv("OMC_CONDA_ARCH", ctx->system.arch, 1); - setenv("OMC_CONDA_PLATFORM", ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], 1); - setenv("OMC_CONDA_PLATFORM_SUBDIR", ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], 1); - - // Register template variables - // These were moved out of main() because we can't take the address of system.platform[x] - // _before_ the array has been initialized. - tpl_register("system.arch", &ctx->system.arch); - tpl_register("system.platform", &ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); - - return 0; -} - -static int populate_mission_ini(struct Delivery **ctx) { - union INIVal val; - struct INIFILE *ini; - - if ((*ctx)->_omc_ini_fp.mission) { - return 0; - } - - // Now populate the rules - char missionfile[PATH_MAX] = {0}; - if (getenv("OMC_SYSCONFDIR")) { - sprintf(missionfile, "%s/%s/%s/%s.ini", - getenv("OMC_SYSCONFDIR"), "mission", (*ctx)->meta.mission, (*ctx)->meta.mission); - } else { - sprintf(missionfile, "%s/%s/%s/%s.ini", - globals.sysconfdir, "mission", (*ctx)->meta.mission, (*ctx)->meta.mission); - } - - msg(OMC_MSG_L2, "Reading mission configuration: %s\n", missionfile); - (*ctx)->_omc_ini_fp.mission = ini_open(missionfile); - ini = (*ctx)->_omc_ini_fp.mission; - if (!ini) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Failed to read misson configuration: %s, %s\n", missionfile, strerror(errno)); - exit(1); - } - (*ctx)->_omc_ini_fp.mission_path = strdup(missionfile); - - ini_getval_required(ini, "meta", "release_fmt", INIVAL_TYPE_STR, &val); - conv_str(&(*ctx)->rules.release_fmt, val); - - // Used for setting artifactory build info - ini_getval_required(ini, "meta", "build_name_fmt", INIVAL_TYPE_STR, &val); - conv_str(&(*ctx)->rules.build_name_fmt, val); - - // Used for setting artifactory build info - ini_getval_required(ini, "meta", "build_number_fmt", INIVAL_TYPE_STR, &val); - conv_str(&(*ctx)->rules.build_number_fmt, val); - return 0; -} - -void validate_delivery_ini(struct INIFILE *ini) { - if (ini_section_search(&ini, INI_SEARCH_EXACT, "meta")) { - ini_has_key_required(ini, "meta", "name"); - ini_has_key_required(ini, "meta", "version"); - ini_has_key_required(ini, "meta", "rc"); - ini_has_key_required(ini, "meta", "mission"); - ini_has_key_required(ini, "meta", "python"); - } else { - SYSERROR("%s", "[meta] configuration section is required"); - exit(1); - } - - if (ini_section_search(&ini, INI_SEARCH_EXACT, "conda")) { - ini_has_key_required(ini, "conda", "installer_name"); - ini_has_key_required(ini, "conda", "installer_version"); - ini_has_key_required(ini, "conda", "installer_platform"); - ini_has_key_required(ini, "conda", "installer_arch"); - } else { - SYSERROR("%s", "[conda] configuration section is required"); - exit(1); - } - - for (size_t i = 0; i < ini->section_count; i++) { - struct INISection *section = ini->section[i]; - if (section && startswith(section->key, "test:")) { - char *name = strstr(section->key, ":"); - if (name && strlen(name) > 1) { - name = &name[1]; - } - ini_has_key_required(ini, section->key, "version"); - ini_has_key_required(ini, section->key, "repository"); - ini_has_key_required(ini, section->key, "script"); - } - } - - if (ini_section_search(&ini, INI_SEARCH_EXACT, "deploy:docker")) { - // yeah? - } - - for (size_t i = 0; i < ini->section_count; i++) { - struct INISection *section = ini->section[i]; - if (section && startswith(section->key, "deploy:artifactory")) { - ini_has_key_required(ini, section->key, "files"); - ini_has_key_required(ini, section->key, "dest"); - } - } -} - -static int populate_delivery_ini(struct Delivery *ctx) { - union INIVal val; - struct INIFILE *ini = ctx->_omc_ini_fp.delivery; - struct INIData *rtdata; - RuntimeEnv *rt; - - validate_delivery_ini(ini); - // Populate runtime variables first they may be interpreted by other - // keys in the configuration - rt = runtime_copy(__environ); - while ((rtdata = ini_getall(ini, "runtime")) != NULL) { - char rec[OMC_BUFSIZ]; - sprintf(rec, "%s=%s", lstrip(strip(rtdata->key)), lstrip(strip(rtdata->value))); - runtime_set(rt, rtdata->key, rtdata->value); - } - runtime_apply(rt); - ctx->runtime.environ = rt; - - ini_getval_required(ini, "meta", "mission", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.mission, val); - - if (!strcasecmp(ctx->meta.mission, "hst")) { - ini_getval(ini, "meta", "codename", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.codename, val); - } else { - ctx->meta.codename = NULL; - } - - /* - if (!strcasecmp(ctx->meta.mission, "jwst")) { - ini_getval(ini, "meta", "version", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.version, val); - - } else { - ctx->meta.version = NULL; - } - */ - ini_getval(ini, "meta", "version", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.version, val); - - ini_getval_required(ini, "meta", "name", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.name, val); - - ini_getval(ini, "meta", "rc", INIVAL_TYPE_INT, &val); - conv_int(&ctx->meta.rc, val); - - ini_getval(ini, "meta", "final", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->meta.final, val); - - ini_getval(ini, "meta", "based_on", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.based_on, val); - - if (!ctx->meta.python) { - ini_getval(ini, "meta", "python", INIVAL_TYPE_STR, &val); - conv_str(&ctx->meta.python, val); - guard_free(ctx->meta.python_compact); - ctx->meta.python_compact = to_short_version(ctx->meta.python); - } else { - ini_setval(&ini, INI_SETVAL_REPLACE, "meta", "python", ctx->meta.python); - } - - ini_getval_required(ini, "conda", "installer_name", INIVAL_TYPE_STR, &val); - conv_str(&ctx->conda.installer_name, val); - - ini_getval_required(ini, "conda", "installer_version", INIVAL_TYPE_STR, &val); - conv_str(&ctx->conda.installer_version, val); - - ini_getval_required(ini, "conda", "installer_platform", INIVAL_TYPE_STR, &val); - conv_str(&ctx->conda.installer_platform, val); - - ini_getval_required(ini, "conda", "installer_arch", INIVAL_TYPE_STR, &val); - conv_str(&ctx->conda.installer_arch, val); - - ini_getval_required(ini, "conda", "installer_baseurl", INIVAL_TYPE_STR, &val); - conv_str(&ctx->conda.installer_baseurl, val); - - ini_getval(ini, "conda", "conda_packages", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&ctx->conda.conda_packages, LINE_SEP, val); - - for (size_t i = 0; i < strlist_count(ctx->conda.conda_packages); i++) { - char *pkg = strlist_item(ctx->conda.conda_packages, i); - if (strpbrk(pkg, ";#")) { - strlist_remove(ctx->conda.conda_packages, i); - } - } - - ini_getval(ini, "conda", "pip_packages", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&ctx->conda.pip_packages, LINE_SEP, val); - - for (size_t i = 0; i < strlist_count(ctx->conda.pip_packages); i++) { - char *pkg = strlist_item(ctx->conda.pip_packages, i); - if (strpbrk(pkg, ";#")) { - strlist_remove(ctx->conda.pip_packages, i); - } - } - - // Delivery metadata consumed - populate_mission_ini(&ctx); - - if (ctx->info.release_name) { - guard_free(ctx->info.release_name); - guard_free(ctx->info.build_name); - guard_free(ctx->info.build_number); - } - - 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); - return -1; - } - - if (!ctx->info.build_name) { - delivery_format_str(ctx, &ctx->info.build_name, ctx->rules.build_name_fmt); - } - if (!ctx->info.build_number) { - delivery_format_str(ctx, &ctx->info.build_number, ctx->rules.build_number_fmt); - } - - // Best I can do to make output directories unique. Annoying. - delivery_init_dirs_stage2(ctx); - - if (!ctx->conda.conda_packages_defer) { - ctx->conda.conda_packages_defer = strlist_init(); - } - if (!ctx->conda.pip_packages_defer) { - ctx->conda.pip_packages_defer = strlist_init(); - } - - for (size_t z = 0, i = 0; i < ini->section_count; i++) { - if (startswith(ini->section[i]->key, "test:")) { - val.as_char_p = strchr(ini->section[i]->key, ':') + 1; - if (val.as_char_p && isempty(val.as_char_p)) { - return 1; - } - conv_str(&ctx->tests[z].name, val); - - ini_getval_required(ini, ini->section[i]->key, "version", INIVAL_TYPE_STR, &val); - conv_str(&ctx->tests[z].version, val); - - ini_getval_required(ini, ini->section[i]->key, "repository", INIVAL_TYPE_STR, &val); - conv_str(&ctx->tests[z].repository, val); - - ini_getval_required(ini, ini->section[i]->key, "script", INIVAL_TYPE_STR, &val); - conv_str_noexpand(&ctx->tests[z].script, val); - - ini_getval(ini, ini->section[i]->key, "repository_remove_tags", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&ctx->tests[z].repository_remove_tags, LINE_SEP, val); - - ini_getval(ini, ini->section[i]->key, "build_recipe", INIVAL_TYPE_STR, &val); - conv_str(&ctx->tests[z].build_recipe, val); - - ini_getval(ini, ini->section[i]->key, "runtime", INIVAL_TO_LIST, &val); - conv_strlist(&ctx->tests[z].runtime.environ, LINE_SEP, val); - z++; - } - } - - for (size_t z = 0, i = 0; i < ini->section_count; i++) { - if (startswith(ini->section[i]->key, "deploy:artifactory")) { - // Artifactory base configuration - ini_getval(ini, ini->section[i]->key, "workaround_parent_only", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.workaround_parent_only, val); - - ini_getval(ini, ini->section[i]->key, "exclusions", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.jfrog[z].upload_ctx.exclusions, val); - - ini_getval(ini, ini->section[i]->key, "explode", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.explode, val); - - ini_getval(ini, ini->section[i]->key, "recursive", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.recursive, val); - - ini_getval(ini, ini->section[i]->key, "retries", INIVAL_TYPE_INT, &val); - conv_int(&ctx->deploy.jfrog[z].upload_ctx.retries, val); - - ini_getval(ini, ini->section[i]->key, "retry_wait_time", INIVAL_TYPE_INT, &val); - conv_int(&ctx->deploy.jfrog[z].upload_ctx.retry_wait_time, val); - - ini_getval(ini, ini->section[i]->key, "detailed_summary", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.detailed_summary, val); - - ini_getval(ini, ini->section[i]->key, "quiet", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.quiet, val); - - ini_getval(ini, ini->section[i]->key, "regexp", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.regexp, val); - - ini_getval(ini, ini->section[i]->key, "spec", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.jfrog[z].upload_ctx.spec, val); - - ini_getval(ini, ini->section[i]->key, "flat", INIVAL_TYPE_BOOL, &val); - conv_bool(&ctx->deploy.jfrog[z].upload_ctx.flat, val); - - ini_getval(ini, ini->section[i]->key, "repo", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.jfrog[z].repo, val); - - ini_getval(ini, ini->section[i]->key, "dest", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.jfrog[z].dest, val); - - ini_getval(ini, ini->section[i]->key, "files", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&ctx->deploy.jfrog[z].files, LINE_SEP, val); - z++; - } - } - - for (size_t i = 0; i < ini->section_count; i++) { - if (startswith(ini->section[i]->key, "deploy:docker")) { - ini_getval(ini, ini->section[i]->key, "registry", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.docker.registry, val); - - ini_getval(ini, ini->section[i]->key, "image_compression", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.docker.image_compression, val); - - ini_getval(ini, ini->section[i]->key, "test_script", INIVAL_TYPE_STR, &val); - conv_str(&ctx->deploy.docker.test_script, val); - - ini_getval(ini, ini->section[i]->key, "build_args", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&ctx->deploy.docker.build_args, LINE_SEP, val); - - ini_getval(ini, ini->section[i]->key, "tags", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&ctx->deploy.docker.tags, LINE_SEP, val); - } - } - return 0; -} - -static int populate_delivery_cfg(struct Delivery *ctx) { - union INIVal val; - struct INIFILE *cfg = ctx->_omc_ini_fp.cfg; - if (!cfg) { - return -1; - } - ini_getval(cfg, "default", "conda_staging_dir", INIVAL_TYPE_STR, &val); - conv_str(&ctx->storage.conda_staging_dir, val); - ini_getval(cfg, "default", "conda_staging_url", INIVAL_TYPE_STR, &val); - conv_str(&ctx->storage.conda_staging_url, val); - ini_getval(cfg, "default", "wheel_staging_dir", INIVAL_TYPE_STR, &val); - conv_str(&ctx->storage.wheel_staging_dir, val); - ini_getval(cfg, "default", "wheel_staging_url", INIVAL_TYPE_STR, &val); - conv_str(&ctx->storage.wheel_staging_url, val); - ini_getval(cfg, "default", "conda_fresh_start", INIVAL_TYPE_BOOL, &val); - conv_bool(&globals.conda_fresh_start, val); - // Below can also be toggled by command-line arguments - if (!globals.continue_on_error) { - ini_getval(cfg, "default", "continue_on_error", INIVAL_TYPE_BOOL, &val); - conv_bool(&globals.continue_on_error, val); - } - // Below can also be toggled by command-line arguments - if (!globals.always_update_base_environment) { - ini_getval(cfg, "default", "always_update_base_environment", INIVAL_TYPE_BOOL, &val); - conv_bool(&globals.always_update_base_environment, val); - } - ini_getval(cfg, "default", "conda_install_prefix", INIVAL_TYPE_STR, &val); - conv_str(&globals.conda_install_prefix, val); - ini_getval(cfg, "default", "conda_packages", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&globals.conda_packages, LINE_SEP, val); - ini_getval(cfg, "default", "pip_packages", INIVAL_TYPE_STR_ARRAY, &val); - conv_strlist(&globals.pip_packages, LINE_SEP, val); - // Configure jfrog cli downloader - ini_getval(cfg, "jfrog_cli_download", "url", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.jfrog_artifactory_base_url, val); - ini_getval(cfg, "jfrog_cli_download", "product", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.jfrog_artifactory_product, val); - ini_getval(cfg, "jfrog_cli_download", "version_series", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.cli_major_ver, val); - ini_getval(cfg, "jfrog_cli_download", "version", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.version, val); - ini_getval(cfg, "jfrog_cli_download", "filename", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.remote_filename, val); - ini_getval(cfg, "deploy:artifactory", "url", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.url, val); - ini_getval(cfg, "deploy:artifactory", "repo", INIVAL_TYPE_STR, &val); - conv_str(&globals.jfrog.repo, val); - return 0; -} - -static int populate_info(struct Delivery *ctx) { - if (!ctx->info.time_str_epoch) { - // Record timestamp used for release - time(&ctx->info.time_now); - ctx->info.time_info = localtime(&ctx->info.time_now); - - ctx->info.time_str_epoch = calloc(OMC_TIME_STR_MAX, sizeof(*ctx->info.time_str_epoch)); - if (!ctx->info.time_str_epoch) { - msg(OMC_MSG_ERROR, "Unable to allocate memory for Unix epoch string\n"); - return -1; - } - snprintf(ctx->info.time_str_epoch, OMC_TIME_STR_MAX - 1, "%li", ctx->info.time_now); - } - return 0; -} - -int *bootstrap_build_info(struct Delivery *ctx) { - struct Delivery local; - memset(&local, 0, sizeof(local)); - local._omc_ini_fp.cfg = ini_open(ctx->_omc_ini_fp.cfg_path); - local._omc_ini_fp.delivery = ini_open(ctx->_omc_ini_fp.delivery_path); - delivery_init_platform(&local); - populate_delivery_cfg(&local); - populate_delivery_ini(&local); - populate_info(&local); - ctx->info.build_name = strdup(local.info.build_name); - ctx->info.build_number = strdup(local.info.build_number); - ctx->info.release_name = strdup(local.info.release_name); - memcpy(&ctx->info.time_info, &local.info.time_info, sizeof(ctx->info.time_info)); - ctx->info.time_now = local.info.time_now; - ctx->info.time_str_epoch = strdup(local.info.time_str_epoch); - delivery_free(&local); - return 0; -} - -int delivery_init(struct Delivery *ctx) { - populate_info(ctx); - populate_delivery_cfg(ctx); - - // Set artifactory URL via environment variable if possible - char *jfurl = getenv("OMC_JF_ARTIFACTORY_URL"); - if (jfurl) { - if (globals.jfrog.url) { - guard_free(globals.jfrog.url); - } - globals.jfrog.url = strdup(jfurl); - } - - // Set artifactory repository via environment if possible - char *jfrepo = getenv("OMC_JF_REPO"); - if (jfrepo) { - if (globals.jfrog.repo) { - guard_free(globals.jfrog.repo); - } - globals.jfrog.repo = strdup(jfrepo); - } - - // Configure architecture and platform information - delivery_init_platform(ctx); - - // Create OMC directory structure - delivery_init_dirs_stage1(ctx); - - char config_local[PATH_MAX]; - sprintf(config_local, "%s/%s", ctx->storage.tmpdir, "config"); - setenv("XDG_CONFIG_HOME", config_local, 1); - - char cache_local[PATH_MAX]; - sprintf(cache_local, "%s/%s", ctx->storage.tmpdir, "cache"); - setenv("XDG_CACHE_HOME", ctx->storage.tmpdir, 1); - - // add tools to PATH - char pathvar_tmp[OMC_BUFSIZ]; - sprintf(pathvar_tmp, "%s/bin:%s", ctx->storage.tools_dir, getenv("PATH")); - setenv("PATH", pathvar_tmp, 1); - - // Prevent git from paginating output - setenv("GIT_PAGER", "", 1); - - populate_delivery_ini(ctx); - - if (ctx->deploy.docker.tags) { - for (size_t i = 0; i < strlist_count(ctx->deploy.docker.tags); i++) { - char *item = strlist_item(ctx->deploy.docker.tags, i); - tolower_s(item); - } - } - - if (ctx->deploy.docker.image_compression) { - if (docker_validate_compression_program(ctx->deploy.docker.image_compression)) { - SYSERROR("[deploy:docker].image_compression - invalid command / program is not installed: %s", ctx->deploy.docker.image_compression); - return -1; - } - } - return 0; -} - -int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt) { - size_t fmt_len = strlen(fmt); - - if (!*dest) { - *dest = calloc(OMC_NAME_MAX, sizeof(**dest)); - if (!*dest) { - return -1; - } - } - - for (size_t i = 0; i < fmt_len; i++) { - if (fmt[i] == '%' && strlen(&fmt[i])) { - i++; - switch (fmt[i]) { - case 'n': // name - strcat(*dest, ctx->meta.name); - break; - case 'c': // codename - strcat(*dest, ctx->meta.codename); - break; - case 'm': // mission - strcat(*dest, ctx->meta.mission); - break; - case 'r': // revision - sprintf(*dest + strlen(*dest), "%d", ctx->meta.rc); - break; - case 'R': // "final"-aware revision - if (ctx->meta.final) - strcat(*dest, "final"); - else - sprintf(*dest + strlen(*dest), "%d", ctx->meta.rc); - break; - case 'v': // version - strcat(*dest, ctx->meta.version); - break; - case 'P': // python version - strcat(*dest, ctx->meta.python); - break; - case 'p': // python version major/minor - strcat(*dest, ctx->meta.python_compact); - break; - case 'a': // system architecture name - strcat(*dest, ctx->system.arch); - break; - case 'o': // system platform (OS) name - strcat(*dest, ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); - break; - case 't': // unix epoch - sprintf(*dest + 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]); - break; - } - } else { // write non-format text - sprintf(*dest + strlen(*dest), "%c", fmt[i]); - } - } - return 0; -} - -void delivery_debug_show(struct Delivery *ctx) { - printf("\n====DEBUG====\n"); - printf("%-20s %-10s\n", "System configuration directory:", globals.sysconfdir); - printf("%-20s %-10s\n", "Mission directory:", ctx->storage.mission_dir); - printf("%-20s %-10s\n", "Testing enabled:", globals.enable_testing ? "Yes" : "No"); - printf("%-20s %-10s\n", "Docker image builds enabled:", globals.enable_docker ? "Yes" : "No"); - printf("%-20s %-10s\n", "Artifact uploading enabled:", globals.enable_artifactory ? "Yes" : "No"); -} - -void delivery_meta_show(struct Delivery *ctx) { - if (globals.verbose) { - delivery_debug_show(ctx); - } - - printf("\n====DELIVERY====\n"); - printf("%-20s %-10s\n", "Target Python:", ctx->meta.python); - printf("%-20s %-10s\n", "Name:", ctx->meta.name); - printf("%-20s %-10s\n", "Mission:", ctx->meta.mission); - if (ctx->meta.codename) { - printf("%-20s %-10s\n", "Codename:", ctx->meta.codename); - } - if (ctx->meta.version) { - printf("%-20s %-10s\n", "Version", ctx->meta.version); - } - if (!ctx->meta.final) { - printf("%-20s %-10d\n", "RC Level:", ctx->meta.rc); - } - printf("%-20s %-10s\n", "Final Release:", ctx->meta.final ? "Yes" : "No"); - printf("%-20s %-10s\n", "Based On:", ctx->meta.based_on ? ctx->meta.based_on : "New"); -} - -void delivery_conda_show(struct Delivery *ctx) { - printf("\n====CONDA====\n"); - printf("%-20s %-10s\n", "Prefix:", ctx->storage.conda_install_prefix); - - puts("Native Packages:"); - if (strlist_count(ctx->conda.conda_packages)) { - for (size_t i = 0; i < strlist_count(ctx->conda.conda_packages); i++) { - char *token = strlist_item(ctx->conda.conda_packages, i); - if (isempty(token) || isblank(*token) || startswith(token, "-")) { - continue; - } - printf("%21s%s\n", "", token); - } - } else { - printf("%21s%s\n", "", "N/A"); - } - - puts("Python Packages:"); - if (strlist_count(ctx->conda.pip_packages)) { - for (size_t i = 0; i < strlist_count(ctx->conda.pip_packages); i++) { - char *token = strlist_item(ctx->conda.pip_packages, i); - if (isempty(token) || isblank(*token) || startswith(token, "-")) { - continue; - } - printf("%21s%s\n", "", token); - } - } else { - printf("%21s%s\n", "", "N/A"); - } -} - -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) { - continue; - } - printf("%-20s %-20s %s\n", ctx->tests[i].name, - ctx->tests[i].version, - ctx->tests[i].repository); - } -} - -void delivery_runtime_show(struct Delivery *ctx) { - printf("\n====RUNTIME====\n"); - struct StrList *rt = NULL; - rt = strlist_copy(ctx->runtime.environ); - if (!rt) { - // no data - return; - } - strlist_sort(rt, OMC_SORT_ALPHA); - size_t total = strlist_count(rt); - for (size_t i = 0; i < total; i++) { - char *item = strlist_item(rt, i); - if (!item) { - // not supposed to occur - msg(OMC_MSG_WARN | OMC_MSG_L1, "Encountered unexpected NULL at record %zu of %zu of runtime array.\n", i); - return; - } - printf("%s\n", item); - } -} - -int delivery_build_recipes(struct Delivery *ctx) { - for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { - char *recipe_dir = NULL; - if (ctx->tests[i].build_recipe) { // build a conda recipe - int recipe_type; - int status; - 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); - return -1; - } - recipe_type = recipe_get_type(recipe_dir); - pushd(recipe_dir); - { - if (RECIPE_TYPE_ASTROCONDA == recipe_type) { - pushd(path_basename(ctx->tests[i].repository)); - } else if (RECIPE_TYPE_CONDA_FORGE == recipe_type) { - pushd("recipe"); - } - - char recipe_version[100]; - char recipe_buildno[100]; - char recipe_git_url[PATH_MAX]; - char recipe_git_rev[PATH_MAX]; - - //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); - // 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"); - - unsigned flags = REPLACE_TRUNCATE_AFTER_MATCH; - //file_replace_text("meta.yaml", "{% set version = ", recipe_version); - if (ctx->meta.final) { - sprintf(recipe_version, "{%% set version = \"%s\" %%}", ctx->tests[i].version); - // TODO: replace sha256 of tagged archive - // TODO: leave the recipe unchanged otherwise. in theory this should produce the same conda package hash as conda forge. - // For now, remove the sha256 requirement - file_replace_text("meta.yaml", "sha256:", "\n", flags); - } else { - file_replace_text("meta.yaml", "{% set version = ", recipe_version, flags); - file_replace_text("meta.yaml", " url:", recipe_git_url, flags); - //file_replace_text("meta.yaml", "sha256:", recipe_git_rev); - file_replace_text("meta.yaml", " sha256:", "\n", flags); - file_replace_text("meta.yaml", " number:", recipe_buildno, flags); - } - - char command[PATH_MAX]; - sprintf(command, "mambabuild --python=%s .", ctx->meta.python); - status = conda_exec(command); - if (status) { - return -1; - } - - if (RECIPE_TYPE_GENERIC != recipe_type) { - popd(); - } - popd(); - } - } - if (recipe_dir) { - guard_free(recipe_dir); - } - } - return 0; -} - -static int filter_repo_tags(char *repo, struct StrList *patterns) { - int result = 0; - - if (!pushd(repo)) { - int list_status = 0; - char *tags_raw = shell_output("git tag -l", &list_status); - struct StrList *tags = strlist_init(); - strlist_append_tokenize(tags, tags_raw, LINE_SEP); - - for (size_t i = 0; tags && i < strlist_count(tags); i++) { - char *tag = strlist_item(tags, i); - for (size_t p = 0; p < strlist_count(patterns); p++) { - char *pattern = strlist_item(patterns, p); - int match = fnmatch(pattern, tag, 0); - if (!match) { - char cmd[PATH_MAX] = {0}; - sprintf(cmd, "git tag -d %s", tag); - result += system(cmd); - break; - } - } - } - guard_strlist_free(&tags); - guard_free(tags_raw); - popd(); - } else { - result = -1; - } - return result; -} - -struct StrList *delivery_build_wheels(struct Delivery *ctx) { - struct StrList *result = NULL; - struct Process proc; - memset(&proc, 0, sizeof(proc)); - - result = strlist_init(); - if (!result) { - perror("unable to allocate memory for string list"); - return NULL; - } - - for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { - if (!ctx->tests[i].build_recipe && ctx->tests[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); - git_clone(&proc, ctx->tests[i].repository, srcdir, ctx->tests[i].version); - - 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); - } - - pushd(srcdir); - { - char dname[NAME_MAX]; - char outdir[PATH_MAX]; - char cmd[PATH_MAX * 2]; - memset(dname, 0, sizeof(dname)); - memset(outdir, 0, sizeof(outdir)); - memset(cmd, 0, sizeof(outdir)); - - strcpy(dname, ctx->tests[i].name); - tolower_s(dname); - sprintf(outdir, "%s/%s", ctx->storage.wheel_artifact_dir, dname); - if (mkdirs(outdir, 0755)) { - fprintf(stderr, "failed to create output directory: %s\n", outdir); - } - - 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); - strlist_free(&result); - return NULL; - } - popd(); - } - } - } - return result; -} - -static const struct Test *requirement_from_test(struct Delivery *ctx, const char *name) { - struct Test *result; - - result = NULL; - for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { - if (strstr(name, ctx->tests[i].name)) { - result = &ctx->tests[i]; - break; - } - } - return result; -} - -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[OMC_BUFSIZ]; - char *env_current = getenv("CONDA_DEFAULT_ENV"); - - if (env_current) { - // The requested environment is not the current environment - if (strcmp(env_current, env_name) != 0) { - // Activate the requested environment - printf("Activating: %s\n", env_name); - conda_activate(conda_install_dir, env_name); - runtime_replace(&ctx->runtime.environ, __environ); - } - } - - memset(cmd, 0, sizeof(cmd)); - memset(pkgs, 0, sizeof(pkgs)); - strcat(cmd, "install"); - - typedef int (*Runner)(const char *); - Runner runner = NULL; - if (INSTALL_PKG_CONDA & type) { - runner = conda_exec; - } else if (INSTALL_PKG_PIP & type) { - runner = pip_exec; - } - - if (INSTALL_PKG_CONDA_DEFERRED & type) { - strcat(cmd, " --use-local"); - } 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"); - } - sprintf(cmd + strlen(cmd), " --extra-index-url 'file://%s'", ctx->storage.wheel_artifact_dir); - } - - for (size_t x = 0; manifest[x] != NULL; x++) { - char *name = NULL; - for (size_t p = 0; p < strlist_count(manifest[x]); p++) { - name = strlist_item(manifest[x], p); - strip(name); - if (!strlen(name)) { - continue; - } - if (INSTALL_PKG_PIP_DEFERRED & type) { - const struct Test *info = requirement_from_test(ctx, name); - if (info) { - sprintf(cmd + strlen(cmd), " '%s==%s'", info->name, info->version); - } else { - fprintf(stderr, "Deferred package '%s' is not present in the tested package list!\n", name); - return -1; - } - } else { - if (startswith(name, "--") || startswith(name, "-")) { - sprintf(cmd + strlen(cmd), " %s", name); - } else { - sprintf(cmd + strlen(cmd), " '%s'", name); - } - } - } - int status = runner(cmd); - if (status) { - return status; - } - } - return 0; -} - -void delivery_get_installer_url(struct Delivery *ctx, char *result) { - if (ctx->conda.installer_version) { - // Use version specified by configuration file - sprintf(result, "%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, - ctx->conda.installer_name, - ctx->conda.installer_platform, - ctx->conda.installer_arch); - } - -} - -int delivery_get_installer(struct Delivery *ctx, char *installer_url) { - char script_path[PATH_MAX]; - char *installer = path_basename(installer_url); - - memset(script_path, 0, sizeof(script_path)); - sprintf(script_path, "%s/%s", ctx->storage.tmpdir, installer); - if (access(script_path, F_OK)) { - // Script doesn't exist - int fetch_status = download(installer_url, script_path, NULL); - if (HTTP_ERROR(fetch_status) || fetch_status < 0) { - // download failed - return -1; - } - } else { - msg(OMC_MSG_RESTRICT | OMC_MSG_L3, "Skipped, installer already exists\n", script_path); - } - - ctx->conda.installer_path = strdup(script_path); - if (!ctx->conda.installer_path) { - SYSERROR("Unable to duplicate script_path: '%s'", script_path); - return -1; - } - - return 0; -} - -int delivery_copy_conda_artifacts(struct Delivery *ctx) { - char cmd[OMC_BUFSIZ]; - char conda_build_dir[PATH_MAX]; - char subdir[PATH_MAX]; - memset(cmd, 0, sizeof(cmd)); - 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"); - // 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(OMC_MSG_RESTRICT | OMC_MSG_WARN | OMC_MSG_L3, - "Skipped: 'conda build' has never been executed.\n"); - return 0; - } - - snprintf(cmd, sizeof(cmd) - 1, "rsync -avi --progress %s/%s %s", - conda_build_dir, - ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], - ctx->storage.conda_artifact_dir); - - return system(cmd); -} - -int delivery_copy_wheel_artifacts(struct Delivery *ctx) { - char cmd[PATH_MAX]; - memset(cmd, 0, sizeof(cmd)); - snprintf(cmd, sizeof(cmd) - 1, "rsync -avi --progress %s/*/dist/*.whl %s", - ctx->storage.build_sources_dir, - ctx->storage.wheel_artifact_dir); - return system(cmd); -} - -int delivery_index_wheel_artifacts(struct Delivery *ctx) { - struct dirent *rec; - DIR *dp; - FILE *top_fp; - - dp = opendir(ctx->storage.wheel_artifact_dir); - if (!dp) { - return -1; - } - - // Generate a "dumb" local pypi index that is compatible with: - // pip install --extra-index-url - char top_index[PATH_MAX]; - memset(top_index, 0, sizeof(top_index)); - sprintf(top_index, "%s/index.html", ctx->storage.wheel_artifact_dir); - top_fp = fopen(top_index, "w+"); - if (!top_fp) { - return -2; - } - - while ((rec = readdir(dp)) != NULL) { - // skip directories - if (DT_REG == rec->d_type || !strcmp(rec->d_name, "..") || !strcmp(rec->d_name, ".")) { - continue; - } - - FILE *bottom_fp; - char bottom_index[PATH_MAX * 2]; - memset(bottom_index, 0, sizeof(bottom_index)); - sprintf(bottom_index, "%s/%s/index.html", ctx->storage.wheel_artifact_dir, rec->d_name); - bottom_fp = fopen(bottom_index, "w+"); - if (!bottom_fp) { - return -3; - } - - if (globals.verbose) { - printf("+ %s\n", rec->d_name); - } - // Add record to top level index - fprintf(top_fp, "%s
\n", rec->d_name, rec->d_name); - - char dpath[PATH_MAX * 2]; - memset(dpath, 0, sizeof(dpath)); - sprintf(dpath, "%s/%s", ctx->storage.wheel_artifact_dir, rec->d_name); - struct StrList *packages = listdir(dpath); - if (!packages) { - fclose(top_fp); - fclose(bottom_fp); - return -4; - } - - for (size_t i = 0; i < strlist_count(packages); i++) { - char *package = strlist_item(packages, i); - if (!endswith(package, ".whl")) { - continue; - } - if (globals.verbose) { - printf("`- %s\n", package); - } - // Write record to bottom level index - fprintf(bottom_fp, "%s
\n", package, package); - } - fclose(bottom_fp); - - guard_strlist_free(&packages); - } - closedir(dp); - fclose(top_fp); - return 0; -} - -void delivery_install_conda(char *install_script, char *conda_install_dir) { - struct Process proc; - memset(&proc, 0, sizeof(proc)); - - if (globals.conda_fresh_start) { - if (!access(conda_install_dir, F_OK)) { - // directory exists so remove it - if (rmtree(conda_install_dir)) { - perror("unable to remove previous installation"); - exit(1); - } - - // Proceed with the installation - // -b = batch mode (non-interactive) - char cmd[255] = {0}; - snprintf(cmd, sizeof(cmd) - 1, "%s %s -b -p %s", - find_program("bash"), - install_script, - conda_install_dir); - if (shell_safe(&proc, cmd)) { - fprintf(stderr, "conda installation failed\n"); - exit(1); - } - } - } else { - msg(OMC_MSG_L3, "Conda removal disabled by configuration\n"); - } -} - -void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir) { - if (conda_activate(conda_install_dir, "base")) { - fprintf(stderr, "conda activation failed\n"); - exit(1); - } - - // Setting the CONDARC environment variable appears to be the only consistent - // way to make sure the file is used. Not setting this variable leads to strange - // behavior, especially if a conda environment is already active when OMC is loaded. - char rcpath[PATH_MAX]; - sprintf(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"); - exit(1); - } - - conda_setup_headless(); -} - -void delivery_defer_packages(struct Delivery *ctx, int type) { - struct StrList *dataptr = NULL; - struct StrList *deferred = NULL; - char *name = NULL; - char cmd[PATH_MAX]; - - memset(cmd, 0, sizeof(cmd)); - - char mode[10]; - if (DEFER_CONDA == type) { - dataptr = ctx->conda.conda_packages; - deferred = ctx->conda.conda_packages_defer; - strcpy(mode, "conda"); - } else if (DEFER_PIP == type) { - dataptr = ctx->conda.pip_packages; - deferred = ctx->conda.pip_packages_defer; - strcpy(mode, "pip"); - } - msg(OMC_MSG_L2, "Filtering %s packages by test definition...\n", mode); - - struct StrList *filtered = NULL; - filtered = strlist_init(); - for (size_t i = 0, z = 0; i < strlist_count(dataptr); i++) { - int ignore_pkg = 0; - - name = strlist_item(dataptr, i); - if (!strlen(name) || isblank(*name) || isspace(*name)) { - // no data - continue; - } - msg(OMC_MSG_L3, "package '%s': ", name); - - // Compile a list of packages that are *also* to be tested. - char *version; - for (size_t x = 0; x < sizeof(ctx->tests) / sizeof(ctx->tests[0]); x++) { - version = NULL; - if (ctx->tests[x].name) { - if (strstr(name, ctx->tests[x].name)) { - version = ctx->tests[x].version; - ignore_pkg = 1; - z++; - break; - } - } - } - - if (ignore_pkg) { - char build_at[PATH_MAX]; - if (DEFER_CONDA == type) { - sprintf(build_at, "%s=%s", name, version); - name = build_at; - } - - printf("BUILD FOR HOST\n"); - strlist_append(&deferred, name); - } else { - printf("USE EXISTING\n"); - strlist_append(&filtered, name); - } - } - - if (!strlist_count(deferred)) { - msg(OMC_MSG_WARN | OMC_MSG_L2, "No %s packages were filtered by test definitions\n", mode); - } else { - if (DEFER_CONDA == type) { - strlist_free(&ctx->conda.conda_packages); - ctx->conda.conda_packages = strlist_copy(filtered); - } else if (DEFER_PIP == type) { - strlist_free(&ctx->conda.pip_packages); - ctx->conda.pip_packages = strlist_copy(filtered); - } - } - if (filtered) { - strlist_free(&filtered); - } -} - -const char *release_header = "# delivery_name: %s\n" - "# delivery_fmt: %s\n" - "# creation_time: %s\n" - "# conda_ident: %s\n" - "# conda_build_ident: %s\n"; - -char *delivery_get_release_header(struct Delivery *ctx) { - char output[OMC_BUFSIZ]; - char stamp[100]; - strftime(stamp, sizeof(stamp) - 1, "%c", ctx->info.time_info); - sprintf(output, release_header, - ctx->info.release_name, - ctx->rules.release_fmt, - stamp, - ctx->conda.tool_version, - ctx->conda.tool_build_version); - return strdup(output); -} - -int delivery_dump_metadata(struct Delivery *ctx) { - FILE *fp; - char filename[PATH_MAX]; - sprintf(filename, "%s/meta-%s.omc", ctx->storage.meta_dir, ctx->info.release_name); - fp = fopen(filename, "w+"); - if (!fp) { - return -1; - } - if (globals.verbose) { - printf("%s\n", filename); - } - fprintf(fp, "name %s\n", ctx->meta.name); - fprintf(fp, "version %s\n", ctx->meta.version); - fprintf(fp, "rc %d\n", ctx->meta.rc); - fprintf(fp, "python %s\n", ctx->meta.python); - fprintf(fp, "python_compact %s\n", ctx->meta.python_compact); - fprintf(fp, "mission %s\n", ctx->meta.mission); - fprintf(fp, "codename %s\n", ctx->meta.codename ? ctx->meta.codename : ""); - fprintf(fp, "platform %s %s %s %s\n", - ctx->system.platform[DELIVERY_PLATFORM], - ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], - ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], - ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); - fprintf(fp, "arch %s\n", ctx->system.arch); - fprintf(fp, "time %s\n", ctx->info.time_str_epoch); - fprintf(fp, "release_fmt %s\n", ctx->rules.release_fmt); - fprintf(fp, "release_name %s\n", ctx->info.release_name); - fprintf(fp, "build_name_fmt %s\n", ctx->rules.build_name_fmt); - fprintf(fp, "build_name %s\n", ctx->info.build_name); - fprintf(fp, "build_number_fmt %s\n", ctx->rules.build_number_fmt); - fprintf(fp, "build_number %s\n", ctx->info.build_number); - fprintf(fp, "conda_installer_baseurl %s\n", ctx->conda.installer_baseurl); - fprintf(fp, "conda_installer_name %s\n", ctx->conda.installer_name); - fprintf(fp, "conda_installer_version %s\n", ctx->conda.installer_version); - fprintf(fp, "conda_installer_platform %s\n", ctx->conda.installer_platform); - fprintf(fp, "conda_installer_arch %s\n", ctx->conda.installer_arch); - - fclose(fp); - return 0; -} - -void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage) { - char output[PATH_MAX]; - char *header = NULL; - char *tempfile = NULL; - FILE *tp = NULL; - - if (stage == DELIVERY_REWRITE_SPEC_STAGE_1) { - header = delivery_get_release_header(ctx); - if (!header) { - msg(OMC_MSG_ERROR, "failed to generate release header string\n", filename); - exit(1); - } - tempfile = xmkstemp(&tp, "w+"); - if (!tempfile || !tp) { - msg(OMC_MSG_ERROR, "%s: unable to create temporary file\n", strerror(errno)); - exit(1); - } - fprintf(tp, "%s", header); - - // Read the original file - char **contents = file_readlines(filename, 0, 0, NULL); - if (!contents) { - msg(OMC_MSG_ERROR, "%s: unable to read %s", filename); - exit(1); - } - - // Write temporary data - for (size_t i = 0; contents[i] != NULL; i++) { - if (startswith(contents[i], "channels:")) { - // Allow for additional conda channel injection - if (ctx->conda.conda_packages_defer && strlist_count(ctx->conda.conda_packages_defer)) { - fprintf(tp, "%s - @CONDA_CHANNEL@\n", contents[i]); - continue; - } - } else if (strstr(contents[i], "- pip:")) { - if (ctx->conda.pip_packages_defer && strlist_count(ctx->conda.pip_packages_defer)) { - // Allow for additional pip argument injection - fprintf(tp, "%s - @PIP_ARGUMENTS@\n", contents[i]); - continue; - } - } else if (startswith(contents[i], "prefix:")) { - // Remove the prefix key - if (strstr(contents[i], "/") || strstr(contents[i], "\\")) { - // path is on the same line as the key - continue; - } else { - // path is on the next line? - if (contents[i + 1] && (strstr(contents[i + 1], "/") || strstr(contents[i + 1], "\\"))) { - i++; - } - continue; - } - } - fprintf(tp, "%s", contents[i]); - } - GENERIC_ARRAY_FREE(contents); - guard_free(header); - fflush(tp); - fclose(tp); - - // 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); - exit(1); - } - remove(tempfile); - guard_free(tempfile); - } else if (stage == DELIVERY_REWRITE_SPEC_STAGE_2) { - // Replace "local" channel with the staging URL - if (ctx->storage.conda_staging_url) { - file_replace_text(filename, "@CONDA_CHANNEL@", ctx->storage.conda_staging_url, 0); - } else if (globals.jfrog.repo) { - sprintf(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 { - msg(OMC_MSG_WARN, "conda_staging_url is not configured\n", filename); - file_replace_text(filename, " - @CONDA_CHANNEL@", "", 0); - } - - if (ctx->storage.wheel_staging_url) { - file_replace_text(filename, "@PIP_ARGUMENTS@", ctx->storage.wheel_staging_url, 0); - } else if (globals.jfrog.repo) { - sprintf(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); - } - } -} - -int delivery_index_conda_artifacts(struct Delivery *ctx) { - return conda_index(ctx->storage.conda_artifact_dir); -} - -void delivery_tests_run(struct Delivery *ctx) { - struct Process proc; - memset(&proc, 0, sizeof(proc)); - - if (!ctx->tests[0].name) { - msg(OMC_MSG_WARN | OMC_MSG_L2, "no tests are defined!\n"); - } else { - for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { - if (!ctx->tests[i].name && !ctx->tests[i].repository && !ctx->tests[i].script) { - // skip unused test records - continue; - } - msg(OMC_MSG_L2, "Executing tests for %s %s\n", ctx->tests[i].name, ctx->tests[i].version); - if (!ctx->tests[i].script || !strlen(ctx->tests[i].script)) { - msg(OMC_MSG_WARN | OMC_MSG_L3, "Nothing to do. To fix, declare a 'script' in section: [test:%s]\n", - ctx->tests[i].name); - continue; - } - - char destdir[PATH_MAX]; - sprintf(destdir, "%s/%s", ctx->storage.build_sources_dir, path_basename(ctx->tests[i].repository)); - - if (!access(destdir, F_OK)) { - msg(OMC_MSG_L3, "Purging repository %s\n", destdir); - if (rmtree(destdir)) { - COE_CHECK_ABORT(1, "Unable to remove repository\n"); - } - } - msg(OMC_MSG_L3, "Cloning repository %s\n", ctx->tests[i].repository); - if (!git_clone(&proc, ctx->tests[i].repository, destdir, ctx->tests[i].version)) { - ctx->tests[i].repository_info_tag = strdup(git_describe(destdir)); - ctx->tests[i].repository_info_ref = strdup(git_rev_parse(destdir, "HEAD")); - } else { - COE_CHECK_ABORT(1, "Unable to clone repository\n"); - } - - if (ctx->tests[i].repository_remove_tags && strlist_count(ctx->tests[i].repository_remove_tags)) { - filter_repo_tags(destdir, ctx->tests[i].repository_remove_tags); - } - - if (pushd(destdir)) { - COE_CHECK_ABORT(1, "Unable to enter repository directory\n"); - } else { -#if 1 - int status; - char cmd[PATH_MAX]; - - msg(OMC_MSG_L3, "Testing %s\n", ctx->tests[i].name); - memset(&proc, 0, sizeof(proc)); - - // Apply workaround for tox positional arguments - char *toxconf = NULL; - if (!access("tox.ini", F_OK)) { - if (!fix_tox_conf("tox.ini", &toxconf)) { - msg(OMC_MSG_L3, "Fixing tox positional arguments\n"); - if (!globals.workaround.tox_posargs) { - globals.workaround.tox_posargs = calloc(PATH_MAX, sizeof(*globals.workaround.tox_posargs)); - } else { - memset(globals.workaround.tox_posargs, 0, PATH_MAX); - } - snprintf(globals.workaround.tox_posargs, PATH_MAX - 1, "-c %s --root .", toxconf); - } - } - - // enable trace mode before executing each test script - memset(cmd, 0, sizeof(cmd)); - sprintf(cmd, "set -x ; %s", ctx->tests[i].script); - - char *cmd_rendered = tpl_render(cmd); - if (cmd_rendered) { - if (strcmp(cmd_rendered, cmd) != 0) { - strcpy(cmd, cmd_rendered); - } - guard_free(cmd_rendered); - } - - status = shell(&proc, cmd); - if (status) { - msg(OMC_MSG_ERROR, "Script failure: %s\n%s\n\nExit code: %d\n", ctx->tests[i].name, ctx->tests[i].script, status); - COE_CHECK_ABORT(1, "Test failure"); - } - - if (toxconf) { - remove(toxconf); - guard_free(toxconf); - } - popd(); -#else - msg(OMC_MSG_WARNING | OMC_MSG_L3, "TESTING DISABLED BY CODE!\n"); -#endif - } - } - } -} - -void delivery_gather_tool_versions(struct Delivery *ctx) { - int status = 0; - - // Extract version from tool output - ctx->conda.tool_version = shell_output("conda --version", &status); - if (ctx->conda.tool_version) - strip(ctx->conda.tool_version); - - ctx->conda.tool_build_version = shell_output("conda build --version", &status); - if (ctx->conda.tool_build_version) - strip(ctx->conda.tool_version); -} - -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); - - if (!access(filepath, F_OK)) { - // already have it - msg(OMC_MSG_L3, "Skipped download, %s already exists\n", filepath); - goto delivery_init_artifactory_envsetup; - } - - char *platform = ctx->system.platform[DELIVERY_PLATFORM]; - msg(OMC_MSG_L3, "Downloading %s for %s %s\n", globals.jfrog.remote_filename, platform, ctx->system.arch); - if ((status = artifactory_download_cli(dest, - globals.jfrog.jfrog_artifactory_base_url, - globals.jfrog.jfrog_artifactory_product, - globals.jfrog.cli_major_ver, - globals.jfrog.version, - platform, - ctx->system.arch, - globals.jfrog.remote_filename))) { - remove(filepath); - } - - delivery_init_artifactory_envsetup: - // CI (ridiculously generic, why?) disables interactive prompts and progress bar output - setenv("CI", "1", 1); - - // 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); - setenv("JFROG_CLI_HOME_DIR", path, 1); - - // JFROG_CLI_TEMP_DIR is where the obvious is stored - setenv("JFROG_CLI_TEMP_DIR", ctx->storage.tmpdir, 1); - return status; -} - -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"); - return -1; - } - - for (size_t i = 0; i < sizeof(ctx->deploy.jfrog) / sizeof(*ctx->deploy.jfrog); i++) { - if (!ctx->deploy.jfrog[i].files || !ctx->deploy.jfrog[i].dest) { - break; - } - jfrt_upload_init(&ctx->deploy.jfrog[i].upload_ctx); - - if (!globals.jfrog.repo) { - msg(OMC_MSG_WARN, "Artifactory repository path is not configured!\n"); - fprintf(stderr, "set OMC_JF_REPO environment variable...\nOr append to configuration file:\n\n"); - fprintf(stderr, "[deploy:artifactory]\nrepo = example/generic/repo/path\n\n"); - status++; - break; - } else if (!ctx->deploy.jfrog[i].repo) { - ctx->deploy.jfrog[i].repo = strdup(globals.jfrog.repo); - } - - 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(OMC_MSG_ERROR, "Artifactory repository path is empty. Cannot continue.\n"); - status++; - break; - } - - ctx->deploy.jfrog[i].upload_ctx.workaround_parent_only = true; - ctx->deploy.jfrog[i].upload_ctx.build_name = ctx->info.build_name; - ctx->deploy.jfrog[i].upload_ctx.build_number = ctx->info.build_number; - - char files[PATH_MAX]; - char dest[PATH_MAX]; // repo + remote dir - - if (jfrog_cli_rt_ping(&ctx->deploy.jfrog_auth)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Unable to contact artifactory server: %s\n", ctx->deploy.jfrog_auth.url); - return -1; - } - - if (strlist_count(ctx->deploy.jfrog[i].files)) { - for (size_t f = 0; f < strlist_count(ctx->deploy.jfrog[i].files); f++) { - memset(dest, 0, sizeof(dest)); - memset(files, 0, sizeof(files)); - 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)); - status += jfrog_cli_rt_upload(&ctx->deploy.jfrog_auth, &ctx->deploy.jfrog[i].upload_ctx, files, dest); - } - } - } - - if (!status && ctx->deploy.jfrog[0].files && ctx->deploy.jfrog[0].dest) { - jfrog_cli_rt_build_collect_env(&ctx->deploy.jfrog_auth, ctx->deploy.jfrog[0].upload_ctx.build_name, ctx->deploy.jfrog[0].upload_ctx.build_number); - jfrog_cli_rt_build_publish(&ctx->deploy.jfrog_auth, ctx->deploy.jfrog[0].upload_ctx.build_name, ctx->deploy.jfrog[0].upload_ctx.build_number); - } - - return status; -} - -int delivery_mission_render_files(struct Delivery *ctx) { - if (!ctx->storage.mission_dir) { - fprintf(stderr, "Mission directory is not configured. Context not initialized?\n"); - return -1; - } - struct Data { - char *src; - char *dest; - } data; - struct INIFILE *cfg = ctx->_omc_ini_fp.mission; - union INIVal val; - - memset(&data, 0, sizeof(data)); - data.src = calloc(PATH_MAX, sizeof(*data.src)); - if (!data.src) { - perror("data.src"); - return -1; - } - - for (size_t i = 0; i < cfg->section_count; i++) { - char *section_name = cfg->section[i]->key; - if (!startswith(section_name, "template:")) { - continue; - } - val.as_char_p = strchr(section_name, ':') + 1; - if (val.as_char_p && isempty(val.as_char_p)) { - guard_free(data.src); - return 1; - } - sprintf(data.src, "%s/%s/%s", ctx->storage.mission_dir, ctx->meta.mission, val.as_char_p); - msg(OMC_MSG_L2, "%s\n", data.src); - - ini_getval_required(cfg, section_name, "destination", INIVAL_TYPE_STR, &val); - conv_str(&data.dest, val); - - char *contents; - struct stat st; - if (lstat(data.src, &st)) { - perror(data.src); - guard_free(data.dest); - continue; - } - - contents = calloc(st.st_size + 1, sizeof(*contents)); - if (!contents) { - perror("template file contents"); - guard_free(data.dest); - continue; - } - - FILE *fp; - fp = fopen(data.src, "rb"); - if (!fp) { - perror(data.src); - guard_free(contents); - guard_free(data.dest); - continue; - } - - if (fread(contents, st.st_size, sizeof(*contents), fp) < 1) { - perror("while reading template file"); - guard_free(contents); - guard_free(data.dest); - fclose(fp); - continue; - } - fclose(fp); - - msg(OMC_MSG_L3, "Writing %s\n", data.dest); - if (tpl_render_to_file(contents, data.dest)) { - guard_free(contents); - guard_free(data.dest); - continue; - } - guard_free(contents); - guard_free(data.dest); - } - - guard_free(data.src); - return 0; -} - -int delivery_docker(struct Delivery *ctx) { - if (!docker_capable(&ctx->deploy.docker.capabilities)) { - return -1; - } - char tag[OMC_NAME_MAX]; - char args[PATH_MAX]; - int has_registry = ctx->deploy.docker.registry != NULL; - size_t total_tags = strlist_count(ctx->deploy.docker.tags); - size_t total_build_args = strlist_count(ctx->deploy.docker.build_args); - - if (!has_registry) { - msg(OMC_MSG_WARN | OMC_MSG_L2, "No docker registry defined. You will need to manually retag the resulting image.\n"); - } - - if (!total_tags) { - char default_tag[PATH_MAX]; - msg(OMC_MSG_WARN | OMC_MSG_L2, "No docker tags defined by configuration. Generating default tag(s).\n"); - // 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); - tolower_s(default_tag); - - // Add tag - ctx->deploy.docker.tags = strlist_init(); - strlist_append(&ctx->deploy.docker.tags, default_tag); - - 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); - tolower_s(default_tag); - - // Add tag - strlist_append(&ctx->deploy.docker.tags, default_tag); - } - // regenerate total tag available - total_tags = strlist_count(ctx->deploy.docker.tags); - } - - memset(args, 0, sizeof(args)); - - // 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); - docker_sanitize_tag(tag); - sprintf(args + strlen(args), " -t \"%s\" ", tag); - } - - // Append build arguments to command (i.e. --build-arg "key=value" - for (size_t i = 0; i < total_build_args; i++) { - char *build_arg = strlist_item(ctx->deploy.docker.build_args, i); - if (!build_arg) { - break; - } - sprintf(args + strlen(args), " --build-arg \"%s\" ", build_arg); - } - - // Build the image - char delivery_file[PATH_MAX]; - char dest[PATH_MAX]; - char rsync_cmd[PATH_MAX * 2]; - 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); - if (access(delivery_file, F_OK) < 0) { - fprintf(stderr, "docker build cannot proceed without delivery file: %s\n", delivery_file); - return -1; - } - - sprintf(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)); - return -1; - } - - memset(dest, 0, sizeof(dest)); - sprintf(dest, "%s/packages", ctx->storage.build_docker_dir); - - msg(OMC_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); - if (system(rsync_cmd)) { - fprintf(stderr, "Failed to copy conda artifacts to docker build directory\n"); - return -1; - } - - msg(OMC_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); - if (system(rsync_cmd)) { - fprintf(stderr, "Failed to copy wheel artifactory to docker build directory\n"); - } - - if (docker_build(ctx->storage.build_docker_dir, args, ctx->deploy.docker.capabilities.build)) { - return -1; - } - - // 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)); - docker_sanitize_tag(tag); - - msg(OMC_MSG_L2, "Executing image test script for %s\n", tag); - if (ctx->deploy.docker.test_script) { - if (isempty(ctx->deploy.docker.test_script)) { - msg(OMC_MSG_L2 | OMC_MSG_WARN, "Image test script has no content\n"); - } else { - int state; - if ((state = docker_script(tag, ctx->deploy.docker.test_script, 0))) { - msg(OMC_MSG_L2 | OMC_MSG_ERROR, "Non-zero exit (%d) from test script. %s image archive will not be generated.\n", state >> 8, tag); - // test failed -- don't save the image - return -1; - } - } - } else { - msg(OMC_MSG_L2 | OMC_MSG_WARN, "No image test script defined\n"); - } - - // Test successful, save image - if (docker_save(path_basename(tag), ctx->storage.docker_artifact_dir, ctx->deploy.docker.image_compression)) { - // save failed - return -1; - } - - return 0; -} - -int delivery_fixup_test_results(struct Delivery *ctx) { - struct dirent *rec; - DIR *dp; - - dp = opendir(ctx->storage.results_dir); - if (!dp) { - perror(ctx->storage.results_dir); - return -1; - } - - while ((rec = readdir(dp)) != NULL) { - char path[PATH_MAX]; - memset(path, 0, sizeof(path)); - - if (!strcmp(rec->d_name, ".") || !strcmp(rec->d_name, "..")) { - continue; - } else if (!endswith(rec->d_name, ".xml")) { - continue; - } - - sprintf(path, "%s/%s", ctx->storage.results_dir, rec->d_name); - msg(OMC_MSG_L2, "%s\n", rec->d_name); - if (xml_pretty_print_in_place(path, OMC_XML_PRETTY_PRINT_PROG, OMC_XML_PRETTY_PRINT_ARGS)) { - msg(OMC_MSG_L3 | OMC_MSG_WARN, "Failed to rewrite file '%s'\n", rec->d_name); - } - } - - closedir(dp); - return 0; -} \ No newline at end of file diff --git a/src/delivery.c b/src/delivery.c new file mode 100644 index 0000000..e77d41c --- /dev/null +++ b/src/delivery.c @@ -0,0 +1,2100 @@ +#define _GNU_SOURCE + +#include +#include "core.h" + +extern struct STASIS_GLOBAL globals; + +static void ini_has_key_required(struct INIFILE *ini, const char *section_name, char *key) { + int status = ini_has_key(ini, section_name, key); + if (!status) { + SYSERROR("%s:%s key is required but not defined", section_name, key); + exit(1); + } +} + +static void ini_getval_required(struct INIFILE *ini, char *section_name, char *key, unsigned type, union INIVal *val) { + int status = ini_getval(ini, section_name, key, type, val); + if (status || isempty(val->as_char_p)) { + SYSERROR("%s:%s value is required but not defined", section_name, key); + exit(1); + } +} + +static void conv_int(int *x, union INIVal val) { + *x = val.as_int; +} + +static void conv_str(char **x, union INIVal val) { + if (*x) { + guard_free(*x); + } + if (val.as_char_p) { + char *tplop = tpl_render(val.as_char_p); + if (tplop) { + *x = tplop; + } else { + *x = NULL; + } + } else { + *x = NULL; + } +} + +static void conv_str_noexpand(char **x, union INIVal val) { + if (*x) { + guard_free(*x); + } + *x = strdup(val.as_char_p); +} + +static void conv_strlist(struct StrList **x, char *tok, union INIVal val) { + if (!(*x)) + (*x) = strlist_init(); + if (val.as_char_p) { + char *tplop = tpl_render(val.as_char_p); + if (tplop) { + strip(tplop); + strlist_append_tokenize((*x), tplop, tok); + guard_free(tplop); + } + } +} + +static void conv_bool(bool *x, union INIVal val) { + *x = val.as_bool; +} + +int delivery_init_tmpdir(struct Delivery *ctx) { + char *tmpdir = NULL; + char *x = NULL; + int unusable = 0; + errno = 0; + + x = getenv("TMPDIR"); + if (x) { + guard_free(ctx->storage.tmpdir); + tmpdir = strdup(x); + } else { + tmpdir = ctx->storage.tmpdir; + } + + if (!tmpdir) { + // memory error + return -1; + } + + // 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)); + 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"); + goto l_delivery_init_tmpdir_fatal; + } + + struct statvfs st; + if (statvfs(tmpdir, &st) < 0) { + goto l_delivery_init_tmpdir_fatal; + } + +#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); + 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); + goto l_delivery_init_tmpdir_fatal; + } + + if (!globals.tmpdir) { + globals.tmpdir = strdup(tmpdir); + } + + if (!ctx->storage.tmpdir) { + ctx->storage.tmpdir = strdup(globals.tmpdir); + } + return unusable; + + l_delivery_init_tmpdir_fatal: + unusable = 1; + return unusable; +} + +void delivery_free(struct Delivery *ctx) { + guard_free(ctx->system.arch); + GENERIC_ARRAY_FREE(ctx->system.platform); + guard_free(ctx->meta.name); + guard_free(ctx->meta.version); + guard_free(ctx->meta.codename); + guard_free(ctx->meta.mission); + guard_free(ctx->meta.python); + guard_free(ctx->meta.mission); + guard_free(ctx->meta.python_compact); + guard_free(ctx->meta.based_on); + guard_runtime_free(ctx->runtime.environ); + guard_free(ctx->storage.root); + guard_free(ctx->storage.tmpdir); + guard_free(ctx->storage.delivery_dir); + guard_free(ctx->storage.tools_dir); + guard_free(ctx->storage.package_dir); + guard_free(ctx->storage.results_dir); + guard_free(ctx->storage.output_dir); + guard_free(ctx->storage.conda_install_prefix); + guard_free(ctx->storage.conda_artifact_dir); + guard_free(ctx->storage.conda_staging_dir); + guard_free(ctx->storage.conda_staging_url); + guard_free(ctx->storage.wheel_artifact_dir); + guard_free(ctx->storage.wheel_staging_dir); + guard_free(ctx->storage.wheel_staging_url); + guard_free(ctx->storage.build_dir); + guard_free(ctx->storage.build_recipes_dir); + guard_free(ctx->storage.build_sources_dir); + guard_free(ctx->storage.build_testing_dir); + guard_free(ctx->storage.build_docker_dir); + guard_free(ctx->storage.mission_dir); + guard_free(ctx->storage.docker_artifact_dir); + guard_free(ctx->info.time_str_epoch); + guard_free(ctx->info.build_name); + guard_free(ctx->info.build_number); + guard_free(ctx->info.release_name); + guard_free(ctx->conda.installer_baseurl); + guard_free(ctx->conda.installer_name); + guard_free(ctx->conda.installer_version); + guard_free(ctx->conda.installer_platform); + guard_free(ctx->conda.installer_arch); + guard_free(ctx->conda.installer_path); + guard_free(ctx->conda.tool_version); + guard_free(ctx->conda.tool_build_version); + guard_strlist_free(&ctx->conda.conda_packages); + guard_strlist_free(&ctx->conda.conda_packages_defer); + guard_strlist_free(&ctx->conda.pip_packages); + guard_strlist_free(&ctx->conda.pip_packages_defer); + 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_free(ctx->tests[i].script); + guard_free(ctx->tests[i].build_recipe); + // test-specific runtime variables + guard_runtime_free(ctx->tests[i].runtime.environ); + } + + guard_free(ctx->rules.release_fmt); + guard_free(ctx->rules.build_name_fmt); + guard_free(ctx->rules.build_number_fmt); + + guard_free(ctx->deploy.docker.test_script); + guard_free(ctx->deploy.docker.registry); + guard_free(ctx->deploy.docker.image_compression); + guard_strlist_free(&ctx->deploy.docker.tags); + guard_strlist_free(&ctx->deploy.docker.build_args); + + for (size_t i = 0; i < sizeof(ctx->deploy.jfrog) / sizeof(ctx->deploy.jfrog[0]); i++) { + guard_free(ctx->deploy.jfrog[i].repo); + guard_free(ctx->deploy.jfrog[i].dest); + guard_strlist_free(&ctx->deploy.jfrog[i].files); + } + + if (ctx->_stasis_ini_fp.delivery) { + ini_free(&ctx->_stasis_ini_fp.delivery); + } + guard_free(ctx->_stasis_ini_fp.delivery_path); + + if (ctx->_stasis_ini_fp.cfg) { + // optional extras + ini_free(&ctx->_stasis_ini_fp.cfg); + } + guard_free(ctx->_stasis_ini_fp.cfg_path); + + if (ctx->_stasis_ini_fp.mission) { + ini_free(&ctx->_stasis_ini_fp.mission); + } + guard_free(ctx->_stasis_ini_fp.mission_path); +} + +void delivery_init_dirs_stage2(struct Delivery *ctx) { + path_store(&ctx->storage.build_recipes_dir, PATH_MAX, ctx->storage.build_dir, "recipes"); + path_store(&ctx->storage.build_sources_dir, PATH_MAX, ctx->storage.build_dir, "sources"); + path_store(&ctx->storage.build_testing_dir, PATH_MAX, ctx->storage.build_dir, "testing"); + path_store(&ctx->storage.build_docker_dir, PATH_MAX, ctx->storage.build_dir, "docker"); + + path_store(&ctx->storage.delivery_dir, PATH_MAX, ctx->storage.output_dir, "delivery"); + path_store(&ctx->storage.results_dir, PATH_MAX, ctx->storage.output_dir, "results"); + path_store(&ctx->storage.package_dir, PATH_MAX, ctx->storage.output_dir, "packages"); + path_store(&ctx->storage.cfgdump_dir, PATH_MAX, ctx->storage.output_dir, "config"); + path_store(&ctx->storage.meta_dir, PATH_MAX, ctx->storage.output_dir, "meta"); + + path_store(&ctx->storage.conda_artifact_dir, PATH_MAX, ctx->storage.package_dir, "conda"); + path_store(&ctx->storage.wheel_artifact_dir, PATH_MAX, ctx->storage.package_dir, "wheels"); + path_store(&ctx->storage.docker_artifact_dir, PATH_MAX, ctx->storage.package_dir, "docker"); +} + +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"); + exit(1); + } + path_store(&ctx->storage.root, PATH_MAX, rootdir, ctx->info.build_name); + } else { + // use "stasis" in current working directory + path_store(&ctx->storage.root, PATH_MAX, "stasis", ctx->info.build_name); + } + 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) + guard_free(globals.tmpdir); + exit(1); + } + + path_store(&ctx->storage.build_dir, PATH_MAX, ctx->storage.root, "build"); + path_store(&ctx->storage.output_dir, PATH_MAX, ctx->storage.root, "output"); + + if (!ctx->storage.mission_dir) { + path_store(&ctx->storage.mission_dir, PATH_MAX, globals.sysconfdir, "mission"); + } + + if (access(ctx->storage.mission_dir, F_OK)) { + msg(STASIS_MSG_L1, "%s: %s\n", ctx->storage.mission_dir, strerror(errno)); + exit(1); + } + + // Override installation prefix using global configuration key + if (globals.conda_install_prefix && strlen(globals.conda_install_prefix)) { + // user wants a specific path + globals.conda_fresh_start = false; + /* + if (mkdirs(globals.conda_install_prefix, 0755)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "Unable to create directory: %s: %s\n", + strerror(errno), globals.conda_install_prefix); + exit(1); + } + */ + /* + ctx->storage.conda_install_prefix = realpath(globals.conda_install_prefix, NULL); + if (!ctx->storage.conda_install_prefix) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "realpath(): Conda installation prefix reassignment failed\n"); + exit(1); + } + ctx->storage.conda_install_prefix = strdup(globals.conda_install_prefix); + */ + path_store(&ctx->storage.conda_install_prefix, PATH_MAX, globals.conda_install_prefix, "conda"); + } else { + // install conda under the STASIS tree + path_store(&ctx->storage.conda_install_prefix, PATH_MAX, ctx->storage.tools_dir, "conda"); + } +} + +int delivery_init_platform(struct Delivery *ctx) { + msg(STASIS_MSG_L2, "Setting architecture\n"); + char archsuffix[20]; + struct utsname uts; + if (uname(&uts)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "uname() failed: %s\n", 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); + 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])); + } + + ctx->system.arch = strdup(uts.machine); + if (!ctx->system.arch) { + // memory error + return -1; + } + + if (!strcmp(ctx->system.arch, "x86_64")) { + strcpy(archsuffix, "64"); + } else { + strcpy(archsuffix, ctx->system.arch); + } + + 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"); + } else if (!strcmp(ctx->system.platform[DELIVERY_PLATFORM], "Linux")) { + sprintf(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], "linux-%s", archsuffix); + strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], "Linux"); + strcpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], "linux"); + } else { + // Not explicitly supported systems + strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], ctx->system.platform[DELIVERY_PLATFORM]); + strcpy(ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], ctx->system.platform[DELIVERY_PLATFORM]); + strcpy(ctx->system.platform[DELIVERY_PLATFORM_RELEASE], ctx->system.platform[DELIVERY_PLATFORM]); + tolower_s(ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); + } + + // Declare some important bits as environment variables + setenv("STASIS_ARCH", ctx->system.arch, 1); + setenv("STASIS_PLATFORM", ctx->system.platform[DELIVERY_PLATFORM], 1); + setenv("STASIS_CONDA_ARCH", ctx->system.arch, 1); + setenv("STASIS_CONDA_PLATFORM", ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], 1); + setenv("STASIS_CONDA_PLATFORM_SUBDIR", ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], 1); + + // Register template variables + // These were moved out of main() because we can't take the address of system.platform[x] + // _before_ the array has been initialized. + tpl_register("system.arch", &ctx->system.arch); + tpl_register("system.platform", &ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); + + return 0; +} + +static int populate_mission_ini(struct Delivery **ctx) { + union INIVal val; + struct INIFILE *ini; + + if ((*ctx)->_stasis_ini_fp.mission) { + return 0; + } + + // Now populate the rules + char missionfile[PATH_MAX] = {0}; + if (getenv("STASIS_SYSCONFDIR")) { + sprintf(missionfile, "%s/%s/%s/%s.ini", + getenv("STASIS_SYSCONFDIR"), "mission", (*ctx)->meta.mission, (*ctx)->meta.mission); + } else { + sprintf(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); + (*ctx)->_stasis_ini_fp.mission = ini_open(missionfile); + ini = (*ctx)->_stasis_ini_fp.mission; + if (!ini) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Failed to read misson configuration: %s, %s\n", missionfile, strerror(errno)); + exit(1); + } + (*ctx)->_stasis_ini_fp.mission_path = strdup(missionfile); + + ini_getval_required(ini, "meta", "release_fmt", INIVAL_TYPE_STR, &val); + conv_str(&(*ctx)->rules.release_fmt, val); + + // Used for setting artifactory build info + ini_getval_required(ini, "meta", "build_name_fmt", INIVAL_TYPE_STR, &val); + conv_str(&(*ctx)->rules.build_name_fmt, val); + + // Used for setting artifactory build info + ini_getval_required(ini, "meta", "build_number_fmt", INIVAL_TYPE_STR, &val); + conv_str(&(*ctx)->rules.build_number_fmt, val); + return 0; +} + +void validate_delivery_ini(struct INIFILE *ini) { + if (ini_section_search(&ini, INI_SEARCH_EXACT, "meta")) { + ini_has_key_required(ini, "meta", "name"); + ini_has_key_required(ini, "meta", "version"); + ini_has_key_required(ini, "meta", "rc"); + ini_has_key_required(ini, "meta", "mission"); + ini_has_key_required(ini, "meta", "python"); + } else { + SYSERROR("%s", "[meta] configuration section is required"); + exit(1); + } + + if (ini_section_search(&ini, INI_SEARCH_EXACT, "conda")) { + ini_has_key_required(ini, "conda", "installer_name"); + ini_has_key_required(ini, "conda", "installer_version"); + ini_has_key_required(ini, "conda", "installer_platform"); + ini_has_key_required(ini, "conda", "installer_arch"); + } else { + SYSERROR("%s", "[conda] configuration section is required"); + exit(1); + } + + for (size_t i = 0; i < ini->section_count; i++) { + struct INISection *section = ini->section[i]; + if (section && startswith(section->key, "test:")) { + char *name = strstr(section->key, ":"); + if (name && strlen(name) > 1) { + name = &name[1]; + } + ini_has_key_required(ini, section->key, "version"); + ini_has_key_required(ini, section->key, "repository"); + ini_has_key_required(ini, section->key, "script"); + } + } + + if (ini_section_search(&ini, INI_SEARCH_EXACT, "deploy:docker")) { + // yeah? + } + + for (size_t i = 0; i < ini->section_count; i++) { + struct INISection *section = ini->section[i]; + if (section && startswith(section->key, "deploy:artifactory")) { + ini_has_key_required(ini, section->key, "files"); + ini_has_key_required(ini, section->key, "dest"); + } + } +} + +static int populate_delivery_ini(struct Delivery *ctx) { + union INIVal val; + struct INIFILE *ini = ctx->_stasis_ini_fp.delivery; + struct INIData *rtdata; + RuntimeEnv *rt; + + validate_delivery_ini(ini); + // Populate runtime variables first they may be interpreted by other + // keys in the configuration + 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); + ctx->runtime.environ = rt; + + ini_getval_required(ini, "meta", "mission", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.mission, val); + + if (!strcasecmp(ctx->meta.mission, "hst")) { + ini_getval(ini, "meta", "codename", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.codename, val); + } else { + ctx->meta.codename = NULL; + } + + /* + if (!strcasecmp(ctx->meta.mission, "jwst")) { + ini_getval(ini, "meta", "version", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.version, val); + + } else { + ctx->meta.version = NULL; + } + */ + ini_getval(ini, "meta", "version", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.version, val); + + ini_getval_required(ini, "meta", "name", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.name, val); + + ini_getval(ini, "meta", "rc", INIVAL_TYPE_INT, &val); + conv_int(&ctx->meta.rc, val); + + ini_getval(ini, "meta", "final", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->meta.final, val); + + ini_getval(ini, "meta", "based_on", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.based_on, val); + + if (!ctx->meta.python) { + ini_getval(ini, "meta", "python", INIVAL_TYPE_STR, &val); + conv_str(&ctx->meta.python, val); + guard_free(ctx->meta.python_compact); + ctx->meta.python_compact = to_short_version(ctx->meta.python); + } else { + ini_setval(&ini, INI_SETVAL_REPLACE, "meta", "python", ctx->meta.python); + } + + ini_getval_required(ini, "conda", "installer_name", INIVAL_TYPE_STR, &val); + conv_str(&ctx->conda.installer_name, val); + + ini_getval_required(ini, "conda", "installer_version", INIVAL_TYPE_STR, &val); + conv_str(&ctx->conda.installer_version, val); + + ini_getval_required(ini, "conda", "installer_platform", INIVAL_TYPE_STR, &val); + conv_str(&ctx->conda.installer_platform, val); + + ini_getval_required(ini, "conda", "installer_arch", INIVAL_TYPE_STR, &val); + conv_str(&ctx->conda.installer_arch, val); + + ini_getval_required(ini, "conda", "installer_baseurl", INIVAL_TYPE_STR, &val); + conv_str(&ctx->conda.installer_baseurl, val); + + ini_getval(ini, "conda", "conda_packages", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&ctx->conda.conda_packages, LINE_SEP, val); + + for (size_t i = 0; i < strlist_count(ctx->conda.conda_packages); i++) { + char *pkg = strlist_item(ctx->conda.conda_packages, i); + if (strpbrk(pkg, ";#")) { + strlist_remove(ctx->conda.conda_packages, i); + } + } + + ini_getval(ini, "conda", "pip_packages", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&ctx->conda.pip_packages, LINE_SEP, val); + + for (size_t i = 0; i < strlist_count(ctx->conda.pip_packages); i++) { + char *pkg = strlist_item(ctx->conda.pip_packages, i); + if (strpbrk(pkg, ";#")) { + strlist_remove(ctx->conda.pip_packages, i); + } + } + + // Delivery metadata consumed + populate_mission_ini(&ctx); + + if (ctx->info.release_name) { + guard_free(ctx->info.release_name); + guard_free(ctx->info.build_name); + guard_free(ctx->info.build_number); + } + + 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); + return -1; + } + + if (!ctx->info.build_name) { + delivery_format_str(ctx, &ctx->info.build_name, ctx->rules.build_name_fmt); + } + if (!ctx->info.build_number) { + delivery_format_str(ctx, &ctx->info.build_number, ctx->rules.build_number_fmt); + } + + // Best I can do to make output directories unique. Annoying. + delivery_init_dirs_stage2(ctx); + + if (!ctx->conda.conda_packages_defer) { + ctx->conda.conda_packages_defer = strlist_init(); + } + if (!ctx->conda.pip_packages_defer) { + ctx->conda.pip_packages_defer = strlist_init(); + } + + for (size_t z = 0, i = 0; i < ini->section_count; i++) { + if (startswith(ini->section[i]->key, "test:")) { + val.as_char_p = strchr(ini->section[i]->key, ':') + 1; + if (val.as_char_p && isempty(val.as_char_p)) { + return 1; + } + conv_str(&ctx->tests[z].name, val); + + ini_getval_required(ini, ini->section[i]->key, "version", INIVAL_TYPE_STR, &val); + conv_str(&ctx->tests[z].version, val); + + ini_getval_required(ini, ini->section[i]->key, "repository", INIVAL_TYPE_STR, &val); + conv_str(&ctx->tests[z].repository, val); + + ini_getval_required(ini, ini->section[i]->key, "script", INIVAL_TYPE_STR, &val); + conv_str_noexpand(&ctx->tests[z].script, val); + + ini_getval(ini, ini->section[i]->key, "repository_remove_tags", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&ctx->tests[z].repository_remove_tags, LINE_SEP, val); + + ini_getval(ini, ini->section[i]->key, "build_recipe", INIVAL_TYPE_STR, &val); + conv_str(&ctx->tests[z].build_recipe, val); + + ini_getval(ini, ini->section[i]->key, "runtime", INIVAL_TO_LIST, &val); + conv_strlist(&ctx->tests[z].runtime.environ, LINE_SEP, val); + z++; + } + } + + for (size_t z = 0, i = 0; i < ini->section_count; i++) { + if (startswith(ini->section[i]->key, "deploy:artifactory")) { + // Artifactory base configuration + ini_getval(ini, ini->section[i]->key, "workaround_parent_only", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.workaround_parent_only, val); + + ini_getval(ini, ini->section[i]->key, "exclusions", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.jfrog[z].upload_ctx.exclusions, val); + + ini_getval(ini, ini->section[i]->key, "explode", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.explode, val); + + ini_getval(ini, ini->section[i]->key, "recursive", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.recursive, val); + + ini_getval(ini, ini->section[i]->key, "retries", INIVAL_TYPE_INT, &val); + conv_int(&ctx->deploy.jfrog[z].upload_ctx.retries, val); + + ini_getval(ini, ini->section[i]->key, "retry_wait_time", INIVAL_TYPE_INT, &val); + conv_int(&ctx->deploy.jfrog[z].upload_ctx.retry_wait_time, val); + + ini_getval(ini, ini->section[i]->key, "detailed_summary", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.detailed_summary, val); + + ini_getval(ini, ini->section[i]->key, "quiet", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.quiet, val); + + ini_getval(ini, ini->section[i]->key, "regexp", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.regexp, val); + + ini_getval(ini, ini->section[i]->key, "spec", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.jfrog[z].upload_ctx.spec, val); + + ini_getval(ini, ini->section[i]->key, "flat", INIVAL_TYPE_BOOL, &val); + conv_bool(&ctx->deploy.jfrog[z].upload_ctx.flat, val); + + ini_getval(ini, ini->section[i]->key, "repo", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.jfrog[z].repo, val); + + ini_getval(ini, ini->section[i]->key, "dest", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.jfrog[z].dest, val); + + ini_getval(ini, ini->section[i]->key, "files", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&ctx->deploy.jfrog[z].files, LINE_SEP, val); + z++; + } + } + + for (size_t i = 0; i < ini->section_count; i++) { + if (startswith(ini->section[i]->key, "deploy:docker")) { + ini_getval(ini, ini->section[i]->key, "registry", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.docker.registry, val); + + ini_getval(ini, ini->section[i]->key, "image_compression", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.docker.image_compression, val); + + ini_getval(ini, ini->section[i]->key, "test_script", INIVAL_TYPE_STR, &val); + conv_str(&ctx->deploy.docker.test_script, val); + + ini_getval(ini, ini->section[i]->key, "build_args", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&ctx->deploy.docker.build_args, LINE_SEP, val); + + ini_getval(ini, ini->section[i]->key, "tags", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&ctx->deploy.docker.tags, LINE_SEP, val); + } + } + return 0; +} + +static int populate_delivery_cfg(struct Delivery *ctx) { + union INIVal val; + struct INIFILE *cfg = ctx->_stasis_ini_fp.cfg; + if (!cfg) { + return -1; + } + ini_getval(cfg, "default", "conda_staging_dir", INIVAL_TYPE_STR, &val); + conv_str(&ctx->storage.conda_staging_dir, val); + ini_getval(cfg, "default", "conda_staging_url", INIVAL_TYPE_STR, &val); + conv_str(&ctx->storage.conda_staging_url, val); + ini_getval(cfg, "default", "wheel_staging_dir", INIVAL_TYPE_STR, &val); + conv_str(&ctx->storage.wheel_staging_dir, val); + ini_getval(cfg, "default", "wheel_staging_url", INIVAL_TYPE_STR, &val); + conv_str(&ctx->storage.wheel_staging_url, val); + ini_getval(cfg, "default", "conda_fresh_start", INIVAL_TYPE_BOOL, &val); + conv_bool(&globals.conda_fresh_start, val); + // Below can also be toggled by command-line arguments + if (!globals.continue_on_error) { + ini_getval(cfg, "default", "continue_on_error", INIVAL_TYPE_BOOL, &val); + conv_bool(&globals.continue_on_error, val); + } + // Below can also be toggled by command-line arguments + if (!globals.always_update_base_environment) { + ini_getval(cfg, "default", "always_update_base_environment", INIVAL_TYPE_BOOL, &val); + conv_bool(&globals.always_update_base_environment, val); + } + ini_getval(cfg, "default", "conda_install_prefix", INIVAL_TYPE_STR, &val); + conv_str(&globals.conda_install_prefix, val); + ini_getval(cfg, "default", "conda_packages", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&globals.conda_packages, LINE_SEP, val); + ini_getval(cfg, "default", "pip_packages", INIVAL_TYPE_STR_ARRAY, &val); + conv_strlist(&globals.pip_packages, LINE_SEP, val); + // Configure jfrog cli downloader + ini_getval(cfg, "jfrog_cli_download", "url", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.jfrog_artifactory_base_url, val); + ini_getval(cfg, "jfrog_cli_download", "product", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.jfrog_artifactory_product, val); + ini_getval(cfg, "jfrog_cli_download", "version_series", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.cli_major_ver, val); + ini_getval(cfg, "jfrog_cli_download", "version", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.version, val); + ini_getval(cfg, "jfrog_cli_download", "filename", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.remote_filename, val); + ini_getval(cfg, "deploy:artifactory", "url", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.url, val); + ini_getval(cfg, "deploy:artifactory", "repo", INIVAL_TYPE_STR, &val); + conv_str(&globals.jfrog.repo, val); + return 0; +} + +static int populate_info(struct Delivery *ctx) { + if (!ctx->info.time_str_epoch) { + // Record timestamp used for release + time(&ctx->info.time_now); + ctx->info.time_info = localtime(&ctx->info.time_now); + + 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, "Unable to allocate memory for Unix epoch string\n"); + return -1; + } + snprintf(ctx->info.time_str_epoch, STASIS_TIME_STR_MAX - 1, "%li", ctx->info.time_now); + } + return 0; +} + +int *bootstrap_build_info(struct Delivery *ctx) { + struct Delivery local; + memset(&local, 0, sizeof(local)); + local._stasis_ini_fp.cfg = ini_open(ctx->_stasis_ini_fp.cfg_path); + local._stasis_ini_fp.delivery = ini_open(ctx->_stasis_ini_fp.delivery_path); + delivery_init_platform(&local); + populate_delivery_cfg(&local); + populate_delivery_ini(&local); + populate_info(&local); + ctx->info.build_name = strdup(local.info.build_name); + ctx->info.build_number = strdup(local.info.build_number); + ctx->info.release_name = strdup(local.info.release_name); + memcpy(&ctx->info.time_info, &local.info.time_info, sizeof(ctx->info.time_info)); + ctx->info.time_now = local.info.time_now; + ctx->info.time_str_epoch = strdup(local.info.time_str_epoch); + delivery_free(&local); + return 0; +} + +int delivery_init(struct Delivery *ctx) { + populate_info(ctx); + populate_delivery_cfg(ctx); + + // Set artifactory URL via environment variable if possible + char *jfurl = getenv("STASIS_JF_ARTIFACTORY_URL"); + if (jfurl) { + if (globals.jfrog.url) { + guard_free(globals.jfrog.url); + } + globals.jfrog.url = strdup(jfurl); + } + + // Set artifactory repository via environment if possible + char *jfrepo = getenv("STASIS_JF_REPO"); + if (jfrepo) { + if (globals.jfrog.repo) { + guard_free(globals.jfrog.repo); + } + globals.jfrog.repo = strdup(jfrepo); + } + + // Configure architecture and platform information + delivery_init_platform(ctx); + + // Create STASIS directory structure + delivery_init_dirs_stage1(ctx); + + char config_local[PATH_MAX]; + sprintf(config_local, "%s/%s", ctx->storage.tmpdir, "config"); + setenv("XDG_CONFIG_HOME", config_local, 1); + + char cache_local[PATH_MAX]; + sprintf(cache_local, "%s/%s", ctx->storage.tmpdir, "cache"); + setenv("XDG_CACHE_HOME", ctx->storage.tmpdir, 1); + + // add tools to PATH + char pathvar_tmp[STASIS_BUFSIZ]; + sprintf(pathvar_tmp, "%s/bin:%s", ctx->storage.tools_dir, getenv("PATH")); + setenv("PATH", pathvar_tmp, 1); + + // Prevent git from paginating output + setenv("GIT_PAGER", "", 1); + + populate_delivery_ini(ctx); + + if (ctx->deploy.docker.tags) { + for (size_t i = 0; i < strlist_count(ctx->deploy.docker.tags); i++) { + char *item = strlist_item(ctx->deploy.docker.tags, i); + tolower_s(item); + } + } + + if (ctx->deploy.docker.image_compression) { + if (docker_validate_compression_program(ctx->deploy.docker.image_compression)) { + SYSERROR("[deploy:docker].image_compression - invalid command / program is not installed: %s", ctx->deploy.docker.image_compression); + return -1; + } + } + return 0; +} + +int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt) { + size_t fmt_len = strlen(fmt); + + if (!*dest) { + *dest = calloc(STASIS_NAME_MAX, sizeof(**dest)); + if (!*dest) { + return -1; + } + } + + for (size_t i = 0; i < fmt_len; i++) { + if (fmt[i] == '%' && strlen(&fmt[i])) { + i++; + switch (fmt[i]) { + case 'n': // name + strcat(*dest, ctx->meta.name); + break; + case 'c': // codename + strcat(*dest, ctx->meta.codename); + break; + case 'm': // mission + strcat(*dest, ctx->meta.mission); + break; + case 'r': // revision + sprintf(*dest + strlen(*dest), "%d", ctx->meta.rc); + break; + case 'R': // "final"-aware revision + if (ctx->meta.final) + strcat(*dest, "final"); + else + sprintf(*dest + strlen(*dest), "%d", ctx->meta.rc); + break; + case 'v': // version + strcat(*dest, ctx->meta.version); + break; + case 'P': // python version + strcat(*dest, ctx->meta.python); + break; + case 'p': // python version major/minor + strcat(*dest, ctx->meta.python_compact); + break; + case 'a': // system architecture name + strcat(*dest, ctx->system.arch); + break; + case 'o': // system platform (OS) name + strcat(*dest, ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); + break; + case 't': // unix epoch + sprintf(*dest + 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]); + break; + } + } else { // write non-format text + sprintf(*dest + strlen(*dest), "%c", fmt[i]); + } + } + return 0; +} + +void delivery_debug_show(struct Delivery *ctx) { + printf("\n====DEBUG====\n"); + printf("%-20s %-10s\n", "System configuration directory:", globals.sysconfdir); + printf("%-20s %-10s\n", "Mission directory:", ctx->storage.mission_dir); + printf("%-20s %-10s\n", "Testing enabled:", globals.enable_testing ? "Yes" : "No"); + printf("%-20s %-10s\n", "Docker image builds enabled:", globals.enable_docker ? "Yes" : "No"); + printf("%-20s %-10s\n", "Artifact uploading enabled:", globals.enable_artifactory ? "Yes" : "No"); +} + +void delivery_meta_show(struct Delivery *ctx) { + if (globals.verbose) { + delivery_debug_show(ctx); + } + + printf("\n====DELIVERY====\n"); + printf("%-20s %-10s\n", "Target Python:", ctx->meta.python); + printf("%-20s %-10s\n", "Name:", ctx->meta.name); + printf("%-20s %-10s\n", "Mission:", ctx->meta.mission); + if (ctx->meta.codename) { + printf("%-20s %-10s\n", "Codename:", ctx->meta.codename); + } + if (ctx->meta.version) { + printf("%-20s %-10s\n", "Version", ctx->meta.version); + } + if (!ctx->meta.final) { + printf("%-20s %-10d\n", "RC Level:", ctx->meta.rc); + } + printf("%-20s %-10s\n", "Final Release:", ctx->meta.final ? "Yes" : "No"); + printf("%-20s %-10s\n", "Based On:", ctx->meta.based_on ? ctx->meta.based_on : "New"); +} + +void delivery_conda_show(struct Delivery *ctx) { + printf("\n====CONDA====\n"); + printf("%-20s %-10s\n", "Prefix:", ctx->storage.conda_install_prefix); + + puts("Native Packages:"); + if (strlist_count(ctx->conda.conda_packages)) { + for (size_t i = 0; i < strlist_count(ctx->conda.conda_packages); i++) { + char *token = strlist_item(ctx->conda.conda_packages, i); + if (isempty(token) || isblank(*token) || startswith(token, "-")) { + continue; + } + printf("%21s%s\n", "", token); + } + } else { + printf("%21s%s\n", "", "N/A"); + } + + puts("Python Packages:"); + if (strlist_count(ctx->conda.pip_packages)) { + for (size_t i = 0; i < strlist_count(ctx->conda.pip_packages); i++) { + char *token = strlist_item(ctx->conda.pip_packages, i); + if (isempty(token) || isblank(*token) || startswith(token, "-")) { + continue; + } + printf("%21s%s\n", "", token); + } + } else { + printf("%21s%s\n", "", "N/A"); + } +} + +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) { + continue; + } + printf("%-20s %-20s %s\n", ctx->tests[i].name, + ctx->tests[i].version, + ctx->tests[i].repository); + } +} + +void delivery_runtime_show(struct Delivery *ctx) { + printf("\n====RUNTIME====\n"); + struct StrList *rt = NULL; + rt = strlist_copy(ctx->runtime.environ); + if (!rt) { + // no data + return; + } + strlist_sort(rt, STASIS_SORT_ALPHA); + size_t total = strlist_count(rt); + for (size_t i = 0; i < total; i++) { + 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); + return; + } + printf("%s\n", item); + } +} + +int delivery_build_recipes(struct Delivery *ctx) { + for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { + char *recipe_dir = NULL; + if (ctx->tests[i].build_recipe) { // build a conda recipe + int recipe_type; + int status; + 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); + return -1; + } + recipe_type = recipe_get_type(recipe_dir); + pushd(recipe_dir); + { + if (RECIPE_TYPE_ASTROCONDA == recipe_type) { + pushd(path_basename(ctx->tests[i].repository)); + } else if (RECIPE_TYPE_CONDA_FORGE == recipe_type) { + pushd("recipe"); + } + + char recipe_version[100]; + char recipe_buildno[100]; + char recipe_git_url[PATH_MAX]; + char recipe_git_rev[PATH_MAX]; + + //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); + // 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"); + + unsigned flags = REPLACE_TRUNCATE_AFTER_MATCH; + //file_replace_text("meta.yaml", "{% set version = ", recipe_version); + if (ctx->meta.final) { + sprintf(recipe_version, "{%% set version = \"%s\" %%}", ctx->tests[i].version); + // TODO: replace sha256 of tagged archive + // TODO: leave the recipe unchanged otherwise. in theory this should produce the same conda package hash as conda forge. + // For now, remove the sha256 requirement + file_replace_text("meta.yaml", "sha256:", "\n", flags); + } else { + file_replace_text("meta.yaml", "{% set version = ", recipe_version, flags); + file_replace_text("meta.yaml", " url:", recipe_git_url, flags); + //file_replace_text("meta.yaml", "sha256:", recipe_git_rev); + file_replace_text("meta.yaml", " sha256:", "\n", flags); + file_replace_text("meta.yaml", " number:", recipe_buildno, flags); + } + + char command[PATH_MAX]; + sprintf(command, "mambabuild --python=%s .", ctx->meta.python); + status = conda_exec(command); + if (status) { + return -1; + } + + if (RECIPE_TYPE_GENERIC != recipe_type) { + popd(); + } + popd(); + } + } + if (recipe_dir) { + guard_free(recipe_dir); + } + } + return 0; +} + +static int filter_repo_tags(char *repo, struct StrList *patterns) { + int result = 0; + + if (!pushd(repo)) { + int list_status = 0; + char *tags_raw = shell_output("git tag -l", &list_status); + struct StrList *tags = strlist_init(); + strlist_append_tokenize(tags, tags_raw, LINE_SEP); + + for (size_t i = 0; tags && i < strlist_count(tags); i++) { + char *tag = strlist_item(tags, i); + for (size_t p = 0; p < strlist_count(patterns); p++) { + char *pattern = strlist_item(patterns, p); + int match = fnmatch(pattern, tag, 0); + if (!match) { + char cmd[PATH_MAX] = {0}; + sprintf(cmd, "git tag -d %s", tag); + result += system(cmd); + break; + } + } + } + guard_strlist_free(&tags); + guard_free(tags_raw); + popd(); + } else { + result = -1; + } + return result; +} + +struct StrList *delivery_build_wheels(struct Delivery *ctx) { + struct StrList *result = NULL; + struct Process proc; + memset(&proc, 0, sizeof(proc)); + + result = strlist_init(); + if (!result) { + perror("unable to allocate memory for string list"); + return NULL; + } + + for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { + if (!ctx->tests[i].build_recipe && ctx->tests[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); + git_clone(&proc, ctx->tests[i].repository, srcdir, ctx->tests[i].version); + + 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); + } + + pushd(srcdir); + { + char dname[NAME_MAX]; + char outdir[PATH_MAX]; + char cmd[PATH_MAX * 2]; + memset(dname, 0, sizeof(dname)); + memset(outdir, 0, sizeof(outdir)); + memset(cmd, 0, sizeof(outdir)); + + strcpy(dname, ctx->tests[i].name); + tolower_s(dname); + sprintf(outdir, "%s/%s", ctx->storage.wheel_artifact_dir, dname); + if (mkdirs(outdir, 0755)) { + fprintf(stderr, "failed to create output directory: %s\n", outdir); + } + + 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); + strlist_free(&result); + return NULL; + } + popd(); + } + } + } + return result; +} + +static const struct Test *requirement_from_test(struct Delivery *ctx, const char *name) { + struct Test *result; + + result = NULL; + for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { + if (strstr(name, ctx->tests[i].name)) { + result = &ctx->tests[i]; + break; + } + } + return result; +} + +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 *env_current = getenv("CONDA_DEFAULT_ENV"); + + if (env_current) { + // The requested environment is not the current environment + if (strcmp(env_current, env_name) != 0) { + // Activate the requested environment + printf("Activating: %s\n", env_name); + conda_activate(conda_install_dir, env_name); + runtime_replace(&ctx->runtime.environ, __environ); + } + } + + memset(cmd, 0, sizeof(cmd)); + memset(pkgs, 0, sizeof(pkgs)); + strcat(cmd, "install"); + + typedef int (*Runner)(const char *); + Runner runner = NULL; + if (INSTALL_PKG_CONDA & type) { + runner = conda_exec; + } else if (INSTALL_PKG_PIP & type) { + runner = pip_exec; + } + + if (INSTALL_PKG_CONDA_DEFERRED & type) { + strcat(cmd, " --use-local"); + } 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"); + } + sprintf(cmd + strlen(cmd), " --extra-index-url 'file://%s'", ctx->storage.wheel_artifact_dir); + } + + for (size_t x = 0; manifest[x] != NULL; x++) { + char *name = NULL; + for (size_t p = 0; p < strlist_count(manifest[x]); p++) { + name = strlist_item(manifest[x], p); + strip(name); + if (!strlen(name)) { + continue; + } + if (INSTALL_PKG_PIP_DEFERRED & type) { + const struct Test *info = requirement_from_test(ctx, name); + if (info) { + sprintf(cmd + strlen(cmd), " '%s==%s'", info->name, info->version); + } else { + fprintf(stderr, "Deferred package '%s' is not present in the tested package list!\n", name); + return -1; + } + } else { + if (startswith(name, "--") || startswith(name, "-")) { + sprintf(cmd + strlen(cmd), " %s", name); + } else { + sprintf(cmd + strlen(cmd), " '%s'", name); + } + } + } + int status = runner(cmd); + if (status) { + return status; + } + } + return 0; +} + +void delivery_get_installer_url(struct Delivery *ctx, char *result) { + if (ctx->conda.installer_version) { + // Use version specified by configuration file + sprintf(result, "%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, + ctx->conda.installer_name, + ctx->conda.installer_platform, + ctx->conda.installer_arch); + } + +} + +int delivery_get_installer(struct Delivery *ctx, char *installer_url) { + char script_path[PATH_MAX]; + char *installer = path_basename(installer_url); + + memset(script_path, 0, sizeof(script_path)); + sprintf(script_path, "%s/%s", ctx->storage.tmpdir, installer); + if (access(script_path, F_OK)) { + // Script doesn't exist + int fetch_status = download(installer_url, script_path, NULL); + if (HTTP_ERROR(fetch_status) || fetch_status < 0) { + // download failed + return -1; + } + } else { + msg(STASIS_MSG_RESTRICT | STASIS_MSG_L3, "Skipped, installer already exists\n", script_path); + } + + ctx->conda.installer_path = strdup(script_path); + if (!ctx->conda.installer_path) { + SYSERROR("Unable to duplicate script_path: '%s'", script_path); + return -1; + } + + return 0; +} + +int delivery_copy_conda_artifacts(struct Delivery *ctx) { + char cmd[STASIS_BUFSIZ]; + char conda_build_dir[PATH_MAX]; + char subdir[PATH_MAX]; + memset(cmd, 0, sizeof(cmd)); + 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"); + // 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"); + return 0; + } + + snprintf(cmd, sizeof(cmd) - 1, "rsync -avi --progress %s/%s %s", + conda_build_dir, + ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], + ctx->storage.conda_artifact_dir); + + return system(cmd); +} + +int delivery_copy_wheel_artifacts(struct Delivery *ctx) { + char cmd[PATH_MAX]; + memset(cmd, 0, sizeof(cmd)); + snprintf(cmd, sizeof(cmd) - 1, "rsync -avi --progress %s/*/dist/*.whl %s", + ctx->storage.build_sources_dir, + ctx->storage.wheel_artifact_dir); + return system(cmd); +} + +int delivery_index_wheel_artifacts(struct Delivery *ctx) { + struct dirent *rec; + DIR *dp; + FILE *top_fp; + + dp = opendir(ctx->storage.wheel_artifact_dir); + if (!dp) { + return -1; + } + + // Generate a "dumb" local pypi index that is compatible with: + // pip install --extra-index-url + char top_index[PATH_MAX]; + memset(top_index, 0, sizeof(top_index)); + sprintf(top_index, "%s/index.html", ctx->storage.wheel_artifact_dir); + top_fp = fopen(top_index, "w+"); + if (!top_fp) { + return -2; + } + + while ((rec = readdir(dp)) != NULL) { + // skip directories + if (DT_REG == rec->d_type || !strcmp(rec->d_name, "..") || !strcmp(rec->d_name, ".")) { + continue; + } + + FILE *bottom_fp; + char bottom_index[PATH_MAX * 2]; + memset(bottom_index, 0, sizeof(bottom_index)); + sprintf(bottom_index, "%s/%s/index.html", ctx->storage.wheel_artifact_dir, rec->d_name); + bottom_fp = fopen(bottom_index, "w+"); + if (!bottom_fp) { + return -3; + } + + if (globals.verbose) { + printf("+ %s\n", rec->d_name); + } + // Add record to top level index + fprintf(top_fp, "%s
\n", rec->d_name, rec->d_name); + + char dpath[PATH_MAX * 2]; + memset(dpath, 0, sizeof(dpath)); + sprintf(dpath, "%s/%s", ctx->storage.wheel_artifact_dir, rec->d_name); + struct StrList *packages = listdir(dpath); + if (!packages) { + fclose(top_fp); + fclose(bottom_fp); + return -4; + } + + for (size_t i = 0; i < strlist_count(packages); i++) { + char *package = strlist_item(packages, i); + if (!endswith(package, ".whl")) { + continue; + } + if (globals.verbose) { + printf("`- %s\n", package); + } + // Write record to bottom level index + fprintf(bottom_fp, "%s
\n", package, package); + } + fclose(bottom_fp); + + guard_strlist_free(&packages); + } + closedir(dp); + fclose(top_fp); + return 0; +} + +void delivery_install_conda(char *install_script, char *conda_install_dir) { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + + if (globals.conda_fresh_start) { + if (!access(conda_install_dir, F_OK)) { + // directory exists so remove it + if (rmtree(conda_install_dir)) { + perror("unable to remove previous installation"); + exit(1); + } + + // Proceed with the installation + // -b = batch mode (non-interactive) + char cmd[255] = {0}; + snprintf(cmd, sizeof(cmd) - 1, "%s %s -b -p %s", + find_program("bash"), + install_script, + conda_install_dir); + if (shell_safe(&proc, cmd)) { + fprintf(stderr, "conda installation failed\n"); + exit(1); + } + } + } else { + msg(STASIS_MSG_L3, "Conda removal disabled by configuration\n"); + } +} + +void delivery_conda_enable(struct Delivery *ctx, char *conda_install_dir) { + if (conda_activate(conda_install_dir, "base")) { + fprintf(stderr, "conda activation failed\n"); + exit(1); + } + + // Setting the CONDARC environment variable appears to be the only consistent + // way to make sure the file is used. Not setting this variable leads to strange + // behavior, especially if a conda environment is already active when STASIS is loaded. + char rcpath[PATH_MAX]; + sprintf(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"); + exit(1); + } + + conda_setup_headless(); +} + +void delivery_defer_packages(struct Delivery *ctx, int type) { + struct StrList *dataptr = NULL; + struct StrList *deferred = NULL; + char *name = NULL; + char cmd[PATH_MAX]; + + memset(cmd, 0, sizeof(cmd)); + + char mode[10]; + if (DEFER_CONDA == type) { + dataptr = ctx->conda.conda_packages; + deferred = ctx->conda.conda_packages_defer; + strcpy(mode, "conda"); + } else if (DEFER_PIP == type) { + dataptr = ctx->conda.pip_packages; + deferred = ctx->conda.pip_packages_defer; + strcpy(mode, "pip"); + } + msg(STASIS_MSG_L2, "Filtering %s packages by test definition...\n", mode); + + struct StrList *filtered = NULL; + filtered = strlist_init(); + for (size_t i = 0, z = 0; i < strlist_count(dataptr); i++) { + int ignore_pkg = 0; + + name = strlist_item(dataptr, i); + if (!strlen(name) || isblank(*name) || isspace(*name)) { + // no data + continue; + } + msg(STASIS_MSG_L3, "package '%s': ", name); + + // Compile a list of packages that are *also* to be tested. + char *version; + for (size_t x = 0; x < sizeof(ctx->tests) / sizeof(ctx->tests[0]); x++) { + version = NULL; + if (ctx->tests[x].name) { + if (strstr(name, ctx->tests[x].name)) { + version = ctx->tests[x].version; + ignore_pkg = 1; + z++; + break; + } + } + } + + if (ignore_pkg) { + char build_at[PATH_MAX]; + if (DEFER_CONDA == type) { + sprintf(build_at, "%s=%s", name, version); + name = build_at; + } + + printf("BUILD FOR HOST\n"); + strlist_append(&deferred, name); + } else { + printf("USE EXISTING\n"); + strlist_append(&filtered, name); + } + } + + if (!strlist_count(deferred)) { + msg(STASIS_MSG_WARN | STASIS_MSG_L2, "No %s packages were filtered by test definitions\n", mode); + } else { + if (DEFER_CONDA == type) { + strlist_free(&ctx->conda.conda_packages); + ctx->conda.conda_packages = strlist_copy(filtered); + } else if (DEFER_PIP == type) { + strlist_free(&ctx->conda.pip_packages); + ctx->conda.pip_packages = strlist_copy(filtered); + } + } + if (filtered) { + strlist_free(&filtered); + } +} + +const char *release_header = "# delivery_name: %s\n" + "# delivery_fmt: %s\n" + "# creation_time: %s\n" + "# conda_ident: %s\n" + "# conda_build_ident: %s\n"; + +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, + ctx->info.release_name, + ctx->rules.release_fmt, + stamp, + ctx->conda.tool_version, + ctx->conda.tool_build_version); + return strdup(output); +} + +int delivery_dump_metadata(struct Delivery *ctx) { + FILE *fp; + char filename[PATH_MAX]; + sprintf(filename, "%s/meta-%s.stasis", ctx->storage.meta_dir, ctx->info.release_name); + fp = fopen(filename, "w+"); + if (!fp) { + return -1; + } + if (globals.verbose) { + printf("%s\n", filename); + } + fprintf(fp, "name %s\n", ctx->meta.name); + fprintf(fp, "version %s\n", ctx->meta.version); + fprintf(fp, "rc %d\n", ctx->meta.rc); + fprintf(fp, "python %s\n", ctx->meta.python); + fprintf(fp, "python_compact %s\n", ctx->meta.python_compact); + fprintf(fp, "mission %s\n", ctx->meta.mission); + fprintf(fp, "codename %s\n", ctx->meta.codename ? ctx->meta.codename : ""); + fprintf(fp, "platform %s %s %s %s\n", + ctx->system.platform[DELIVERY_PLATFORM], + ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR], + ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER], + ctx->system.platform[DELIVERY_PLATFORM_RELEASE]); + fprintf(fp, "arch %s\n", ctx->system.arch); + fprintf(fp, "time %s\n", ctx->info.time_str_epoch); + fprintf(fp, "release_fmt %s\n", ctx->rules.release_fmt); + fprintf(fp, "release_name %s\n", ctx->info.release_name); + fprintf(fp, "build_name_fmt %s\n", ctx->rules.build_name_fmt); + fprintf(fp, "build_name %s\n", ctx->info.build_name); + fprintf(fp, "build_number_fmt %s\n", ctx->rules.build_number_fmt); + fprintf(fp, "build_number %s\n", ctx->info.build_number); + fprintf(fp, "conda_installer_baseurl %s\n", ctx->conda.installer_baseurl); + fprintf(fp, "conda_installer_name %s\n", ctx->conda.installer_name); + fprintf(fp, "conda_installer_version %s\n", ctx->conda.installer_version); + fprintf(fp, "conda_installer_platform %s\n", ctx->conda.installer_platform); + fprintf(fp, "conda_installer_arch %s\n", ctx->conda.installer_arch); + + fclose(fp); + return 0; +} + +void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage) { + char output[PATH_MAX]; + char *header = NULL; + char *tempfile = NULL; + FILE *tp = NULL; + + if (stage == DELIVERY_REWRITE_SPEC_STAGE_1) { + header = delivery_get_release_header(ctx); + if (!header) { + msg(STASIS_MSG_ERROR, "failed to generate release header string\n", filename); + exit(1); + } + tempfile = xmkstemp(&tp, "w+"); + if (!tempfile || !tp) { + msg(STASIS_MSG_ERROR, "%s: unable to create temporary file\n", strerror(errno)); + exit(1); + } + fprintf(tp, "%s", header); + + // Read the original file + char **contents = file_readlines(filename, 0, 0, NULL); + if (!contents) { + msg(STASIS_MSG_ERROR, "%s: unable to read %s", filename); + exit(1); + } + + // Write temporary data + for (size_t i = 0; contents[i] != NULL; i++) { + if (startswith(contents[i], "channels:")) { + // Allow for additional conda channel injection + if (ctx->conda.conda_packages_defer && strlist_count(ctx->conda.conda_packages_defer)) { + fprintf(tp, "%s - @CONDA_CHANNEL@\n", contents[i]); + continue; + } + } else if (strstr(contents[i], "- pip:")) { + if (ctx->conda.pip_packages_defer && strlist_count(ctx->conda.pip_packages_defer)) { + // Allow for additional pip argument injection + fprintf(tp, "%s - @PIP_ARGUMENTS@\n", contents[i]); + continue; + } + } else if (startswith(contents[i], "prefix:")) { + // Remove the prefix key + if (strstr(contents[i], "/") || strstr(contents[i], "\\")) { + // path is on the same line as the key + continue; + } else { + // path is on the next line? + if (contents[i + 1] && (strstr(contents[i + 1], "/") || strstr(contents[i + 1], "\\"))) { + i++; + } + continue; + } + } + fprintf(tp, "%s", contents[i]); + } + GENERIC_ARRAY_FREE(contents); + guard_free(header); + fflush(tp); + fclose(tp); + + // 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); + exit(1); + } + remove(tempfile); + guard_free(tempfile); + } else if (stage == DELIVERY_REWRITE_SPEC_STAGE_2) { + // Replace "local" channel with the staging URL + if (ctx->storage.conda_staging_url) { + file_replace_text(filename, "@CONDA_CHANNEL@", ctx->storage.conda_staging_url, 0); + } else if (globals.jfrog.repo) { + sprintf(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 { + msg(STASIS_MSG_WARN, "conda_staging_url is not configured\n", filename); + file_replace_text(filename, " - @CONDA_CHANNEL@", "", 0); + } + + if (ctx->storage.wheel_staging_url) { + file_replace_text(filename, "@PIP_ARGUMENTS@", ctx->storage.wheel_staging_url, 0); + } else if (globals.jfrog.repo) { + sprintf(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); + } + } +} + +int delivery_index_conda_artifacts(struct Delivery *ctx) { + return conda_index(ctx->storage.conda_artifact_dir); +} + +void delivery_tests_run(struct Delivery *ctx) { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + + if (!ctx->tests[0].name) { + msg(STASIS_MSG_WARN | STASIS_MSG_L2, "no tests are defined!\n"); + } else { + for (size_t i = 0; i < sizeof(ctx->tests) / sizeof(ctx->tests[0]); i++) { + if (!ctx->tests[i].name && !ctx->tests[i].repository && !ctx->tests[i].script) { + // skip unused test records + continue; + } + msg(STASIS_MSG_L2, "Executing tests for %s %s\n", ctx->tests[i].name, ctx->tests[i].version); + if (!ctx->tests[i].script || !strlen(ctx->tests[i].script)) { + msg(STASIS_MSG_WARN | STASIS_MSG_L3, "Nothing to do. To fix, declare a 'script' in section: [test:%s]\n", + ctx->tests[i].name); + continue; + } + + char destdir[PATH_MAX]; + sprintf(destdir, "%s/%s", ctx->storage.build_sources_dir, path_basename(ctx->tests[i].repository)); + + if (!access(destdir, F_OK)) { + msg(STASIS_MSG_L3, "Purging repository %s\n", destdir); + if (rmtree(destdir)) { + COE_CHECK_ABORT(1, "Unable to remove repository\n"); + } + } + msg(STASIS_MSG_L3, "Cloning repository %s\n", ctx->tests[i].repository); + if (!git_clone(&proc, ctx->tests[i].repository, destdir, ctx->tests[i].version)) { + ctx->tests[i].repository_info_tag = strdup(git_describe(destdir)); + ctx->tests[i].repository_info_ref = strdup(git_rev_parse(destdir, "HEAD")); + } else { + COE_CHECK_ABORT(1, "Unable to clone repository\n"); + } + + if (ctx->tests[i].repository_remove_tags && strlist_count(ctx->tests[i].repository_remove_tags)) { + filter_repo_tags(destdir, ctx->tests[i].repository_remove_tags); + } + + if (pushd(destdir)) { + COE_CHECK_ABORT(1, "Unable to enter repository directory\n"); + } else { +#if 1 + int status; + char cmd[PATH_MAX]; + + msg(STASIS_MSG_L3, "Testing %s\n", ctx->tests[i].name); + memset(&proc, 0, sizeof(proc)); + + // Apply workaround for tox positional arguments + char *toxconf = NULL; + if (!access("tox.ini", F_OK)) { + if (!fix_tox_conf("tox.ini", &toxconf)) { + msg(STASIS_MSG_L3, "Fixing tox positional arguments\n"); + if (!globals.workaround.tox_posargs) { + globals.workaround.tox_posargs = calloc(PATH_MAX, sizeof(*globals.workaround.tox_posargs)); + } else { + memset(globals.workaround.tox_posargs, 0, PATH_MAX); + } + snprintf(globals.workaround.tox_posargs, PATH_MAX - 1, "-c %s --root .", toxconf); + } + } + + // enable trace mode before executing each test script + memset(cmd, 0, sizeof(cmd)); + sprintf(cmd, "set -x ; %s", ctx->tests[i].script); + + char *cmd_rendered = tpl_render(cmd); + if (cmd_rendered) { + if (strcmp(cmd_rendered, cmd) != 0) { + strcpy(cmd, cmd_rendered); + } + guard_free(cmd_rendered); + } + + status = shell(&proc, cmd); + if (status) { + msg(STASIS_MSG_ERROR, "Script failure: %s\n%s\n\nExit code: %d\n", ctx->tests[i].name, ctx->tests[i].script, status); + COE_CHECK_ABORT(1, "Test failure"); + } + + if (toxconf) { + remove(toxconf); + guard_free(toxconf); + } + popd(); +#else + msg(STASIS_MSG_WARNING | STASIS_MSG_L3, "TESTING DISABLED BY CODE!\n"); +#endif + } + } + } +} + +void delivery_gather_tool_versions(struct Delivery *ctx) { + int status = 0; + + // Extract version from tool output + ctx->conda.tool_version = shell_output("conda --version", &status); + if (ctx->conda.tool_version) + strip(ctx->conda.tool_version); + + ctx->conda.tool_build_version = shell_output("conda build --version", &status); + if (ctx->conda.tool_build_version) + strip(ctx->conda.tool_version); +} + +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); + + if (!access(filepath, F_OK)) { + // already have it + msg(STASIS_MSG_L3, "Skipped download, %s already exists\n", filepath); + goto delivery_init_artifactory_envsetup; + } + + 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, + globals.jfrog.jfrog_artifactory_base_url, + globals.jfrog.jfrog_artifactory_product, + globals.jfrog.cli_major_ver, + globals.jfrog.version, + platform, + ctx->system.arch, + globals.jfrog.remote_filename))) { + remove(filepath); + } + + delivery_init_artifactory_envsetup: + // CI (ridiculously generic, why?) disables interactive prompts and progress bar output + setenv("CI", "1", 1); + + // 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); + setenv("JFROG_CLI_HOME_DIR", path, 1); + + // JFROG_CLI_TEMP_DIR is where the obvious is stored + setenv("JFROG_CLI_TEMP_DIR", ctx->storage.tmpdir, 1); + return status; +} + +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"); + return -1; + } + + for (size_t i = 0; i < sizeof(ctx->deploy.jfrog) / sizeof(*ctx->deploy.jfrog); i++) { + if (!ctx->deploy.jfrog[i].files || !ctx->deploy.jfrog[i].dest) { + break; + } + 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"); + status++; + break; + } else if (!ctx->deploy.jfrog[i].repo) { + ctx->deploy.jfrog[i].repo = strdup(globals.jfrog.repo); + } + + 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"); + status++; + break; + } + + ctx->deploy.jfrog[i].upload_ctx.workaround_parent_only = true; + ctx->deploy.jfrog[i].upload_ctx.build_name = ctx->info.build_name; + ctx->deploy.jfrog[i].upload_ctx.build_number = ctx->info.build_number; + + char files[PATH_MAX]; + char dest[PATH_MAX]; // repo + remote dir + + 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); + return -1; + } + + if (strlist_count(ctx->deploy.jfrog[i].files)) { + for (size_t f = 0; f < strlist_count(ctx->deploy.jfrog[i].files); f++) { + memset(dest, 0, sizeof(dest)); + memset(files, 0, sizeof(files)); + 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)); + status += jfrog_cli_rt_upload(&ctx->deploy.jfrog_auth, &ctx->deploy.jfrog[i].upload_ctx, files, dest); + } + } + } + + if (!status && ctx->deploy.jfrog[0].files && ctx->deploy.jfrog[0].dest) { + jfrog_cli_rt_build_collect_env(&ctx->deploy.jfrog_auth, ctx->deploy.jfrog[0].upload_ctx.build_name, ctx->deploy.jfrog[0].upload_ctx.build_number); + jfrog_cli_rt_build_publish(&ctx->deploy.jfrog_auth, ctx->deploy.jfrog[0].upload_ctx.build_name, ctx->deploy.jfrog[0].upload_ctx.build_number); + } + + return status; +} + +int delivery_mission_render_files(struct Delivery *ctx) { + if (!ctx->storage.mission_dir) { + fprintf(stderr, "Mission directory is not configured. Context not initialized?\n"); + return -1; + } + struct Data { + char *src; + char *dest; + } data; + struct INIFILE *cfg = ctx->_stasis_ini_fp.mission; + union INIVal val; + + memset(&data, 0, sizeof(data)); + data.src = calloc(PATH_MAX, sizeof(*data.src)); + if (!data.src) { + perror("data.src"); + return -1; + } + + for (size_t i = 0; i < cfg->section_count; i++) { + char *section_name = cfg->section[i]->key; + if (!startswith(section_name, "template:")) { + continue; + } + val.as_char_p = strchr(section_name, ':') + 1; + if (val.as_char_p && isempty(val.as_char_p)) { + guard_free(data.src); + return 1; + } + sprintf(data.src, "%s/%s/%s", ctx->storage.mission_dir, ctx->meta.mission, val.as_char_p); + msg(STASIS_MSG_L2, "%s\n", data.src); + + ini_getval_required(cfg, section_name, "destination", INIVAL_TYPE_STR, &val); + conv_str(&data.dest, val); + + char *contents; + struct stat st; + if (lstat(data.src, &st)) { + perror(data.src); + guard_free(data.dest); + continue; + } + + contents = calloc(st.st_size + 1, sizeof(*contents)); + if (!contents) { + perror("template file contents"); + guard_free(data.dest); + continue; + } + + FILE *fp; + fp = fopen(data.src, "rb"); + if (!fp) { + perror(data.src); + guard_free(contents); + guard_free(data.dest); + continue; + } + + if (fread(contents, st.st_size, sizeof(*contents), fp) < 1) { + perror("while reading template file"); + guard_free(contents); + guard_free(data.dest); + fclose(fp); + continue; + } + fclose(fp); + + msg(STASIS_MSG_L3, "Writing %s\n", data.dest); + if (tpl_render_to_file(contents, data.dest)) { + guard_free(contents); + guard_free(data.dest); + continue; + } + guard_free(contents); + guard_free(data.dest); + } + + guard_free(data.src); + return 0; +} + +int delivery_docker(struct Delivery *ctx) { + if (!docker_capable(&ctx->deploy.docker.capabilities)) { + return -1; + } + char tag[STASIS_NAME_MAX]; + char args[PATH_MAX]; + int has_registry = ctx->deploy.docker.registry != NULL; + size_t total_tags = strlist_count(ctx->deploy.docker.tags); + 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 retag the resulting image.\n"); + } + + 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"); + // 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); + tolower_s(default_tag); + + // Add tag + ctx->deploy.docker.tags = strlist_init(); + strlist_append(&ctx->deploy.docker.tags, default_tag); + + 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); + tolower_s(default_tag); + + // Add tag + strlist_append(&ctx->deploy.docker.tags, default_tag); + } + // regenerate total tag available + total_tags = strlist_count(ctx->deploy.docker.tags); + } + + memset(args, 0, sizeof(args)); + + // 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); + docker_sanitize_tag(tag); + sprintf(args + strlen(args), " -t \"%s\" ", tag); + } + + // Append build arguments to command (i.e. --build-arg "key=value" + for (size_t i = 0; i < total_build_args; i++) { + char *build_arg = strlist_item(ctx->deploy.docker.build_args, i); + if (!build_arg) { + break; + } + sprintf(args + strlen(args), " --build-arg \"%s\" ", build_arg); + } + + // Build the image + char delivery_file[PATH_MAX]; + char dest[PATH_MAX]; + char rsync_cmd[PATH_MAX * 2]; + 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); + if (access(delivery_file, F_OK) < 0) { + fprintf(stderr, "docker build cannot proceed without delivery file: %s\n", delivery_file); + return -1; + } + + sprintf(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)); + return -1; + } + + memset(dest, 0, sizeof(dest)); + sprintf(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); + if (system(rsync_cmd)) { + fprintf(stderr, "Failed to copy conda artifacts to docker build directory\n"); + 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); + if (system(rsync_cmd)) { + fprintf(stderr, "Failed to copy wheel artifactory to docker build directory\n"); + } + + if (docker_build(ctx->storage.build_docker_dir, args, ctx->deploy.docker.capabilities.build)) { + return -1; + } + + // 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)); + 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"); + } 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); + // test failed -- don't save the image + return -1; + } + } + } else { + msg(STASIS_MSG_L2 | STASIS_MSG_WARN, "No image test script defined\n"); + } + + // Test successful, save image + if (docker_save(path_basename(tag), ctx->storage.docker_artifact_dir, ctx->deploy.docker.image_compression)) { + // save failed + return -1; + } + + return 0; +} + +int delivery_fixup_test_results(struct Delivery *ctx) { + struct dirent *rec; + DIR *dp; + + dp = opendir(ctx->storage.results_dir); + if (!dp) { + perror(ctx->storage.results_dir); + return -1; + } + + while ((rec = readdir(dp)) != NULL) { + char path[PATH_MAX]; + memset(path, 0, sizeof(path)); + + if (!strcmp(rec->d_name, ".") || !strcmp(rec->d_name, "..")) { + continue; + } else if (!endswith(rec->d_name, ".xml")) { + continue; + } + + sprintf(path, "%s/%s", ctx->storage.results_dir, rec->d_name); + msg(STASIS_MSG_L2, "%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); + } + } + + closedir(dp); + return 0; +} \ No newline at end of file diff --git a/src/docker.c b/src/docker.c index 308fbc7..da7c1ce 100644 --- a/src/docker.c +++ b/src/docker.c @@ -1,4 +1,4 @@ -#include "omc.h" +#include "core.h" #include "docker.h" @@ -9,11 +9,11 @@ int docker_exec(const char *args, unsigned flags) { memset(&proc, 0, sizeof(proc)); memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd) - 1, "docker %s", args); - if (flags & OMC_DOCKER_QUIET) { + if (flags & STASIS_DOCKER_QUIET) { strcpy(proc.f_stdout, "/dev/null"); strcpy(proc.f_stderr, "/dev/null"); } else { - msg(OMC_MSG_L2, "Executing: %s\n", cmd); + msg(STASIS_MSG_L2, "Executing: %s\n", cmd); } shell(&proc, cmd); @@ -25,7 +25,7 @@ int docker_script(const char *image, char *data, unsigned flags) { FILE *infile; FILE *outfile; char cmd[PATH_MAX]; - char buffer[OMC_BUFSIZ]; + char buffer[STASIS_BUFSIZ]; memset(cmd, 0, sizeof(cmd)); snprintf(cmd, sizeof(cmd) - 1, "docker run --rm -i %s /bin/sh -", image); @@ -59,10 +59,10 @@ int docker_build(const char *dirpath, const char *args, int engine) { memset(build, 0, sizeof(build)); memset(cmd, 0, sizeof(cmd)); - if (engine & OMC_DOCKER_BUILD) { + if (engine & STASIS_DOCKER_BUILD) { strcpy(build, "build"); } - if (engine & OMC_DOCKER_BUILD_X) { + if (engine & STASIS_DOCKER_BUILD_X) { strcpy(build, "buildx build"); } snprintf(cmd, sizeof(cmd) - 1, "%s %s %s", build, args, dirpath); @@ -149,7 +149,7 @@ int docker_capable(struct DockerCapabilities *result) { } result->available = true; - if (docker_exec("ps", OMC_DOCKER_QUIET)) { + if (docker_exec("ps", STASIS_DOCKER_QUIET)) { // user cannot connect to the socket return false; } @@ -160,11 +160,11 @@ int docker_capable(struct DockerCapabilities *result) { } guard_free(version); - if (!docker_exec("buildx build --help", OMC_DOCKER_QUIET)) { - result->build |= OMC_DOCKER_BUILD_X; + if (!docker_exec("buildx build --help", STASIS_DOCKER_QUIET)) { + result->build |= STASIS_DOCKER_BUILD_X; } - if (!docker_exec("build --help", OMC_DOCKER_QUIET)) { - result->build |= OMC_DOCKER_BUILD; + if (!docker_exec("build --help", STASIS_DOCKER_QUIET)) { + result->build |= STASIS_DOCKER_BUILD; } if (!result->build) { // can't use docker without a build plugin diff --git a/src/download.c b/src/download.c index cbb617f..1623560 100644 --- a/src/download.c +++ b/src/download.c @@ -17,7 +17,7 @@ long download(char *url, const char *filename, char **errmsg) { long http_code = -1; FILE *fp; char user_agent[20]; - sprintf(user_agent, "omc/%s", VERSION); + sprintf(user_agent, "stasis/%s", VERSION); curl_global_init(CURL_GLOBAL_ALL); c = curl_easy_init(); diff --git a/src/environment.c b/src/environment.c index 824b447..924fbf8 100644 --- a/src/environment.c +++ b/src/environment.c @@ -70,7 +70,7 @@ void runtime_export(RuntimeEnv *env, char **keys) { NULL, }; - char output[OMC_BUFSIZ]; + char output[STASIS_BUFSIZ]; char export_command[7]; // export=6 and setenv=6... convenient char *_sh = getenv("SHELL"); char *sh = path_basename(_sh); @@ -290,9 +290,9 @@ char *runtime_expand_var(RuntimeEnv *env, char *input) { return input; } - expanded = calloc(OMC_BUFSIZ, sizeof(char)); + expanded = calloc(STASIS_BUFSIZ, sizeof(char)); if (expanded == NULL) { - SYSERROR("could not allocate %d bytes for runtime_expand_var buffer", OMC_BUFSIZ); + SYSERROR("could not allocate %d bytes for runtime_expand_var buffer", STASIS_BUFSIZ); return NULL; } diff --git a/src/globals.c b/src/globals.c index 27c8aff..7ed7c3c 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1,29 +1,30 @@ #include #include -#include "omc.h" +#include "core.h" const char *VERSION = "1.0.0"; const char *AUTHOR = "Joseph Hunkeler"; const char *BANNER = - "---------------------------------------------------------------------\n" -#if defined(OMC_DUMB_TERMINAL) - " OH MY CAL \n" + "------------------------------------------------------------------------\n" +#if defined(STASIS_DUMB_TERMINAL) + " STASIS \n" #else - " ██████╗ ██╗ ██╗ ███╗ ███╗██╗ ██╗ ██████╗ █████╗ ██╗ \n" - "██╔═══██╗██║ ██║ ████╗ ████║╚██╗ ██╔╝ ██╔════╝██╔══██╗██║ \n" - "██║ ██║███████║ ██╔████╔██║ ╚████╔╝ ██║ ███████║██║ \n" - "██║ ██║██╔══██║ ██║╚██╔╝██║ ╚██╔╝ ██║ ██╔══██║██║ \n" - "╚██████╔╝██║ ██║ ██║ ╚═╝ ██║ ██║ ╚██████╗██║ ██║███████╗\n" - " ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝\n" + " _____ _______ _____ _____ _____ \n" + " / ____|__ __|/\\ / ____|_ _|/ ____| \n" + " | (___ | | / \\ | (___ | | | (___ \n" + " \\___ \\ | | / /\\ \\ \\___ \\ | | \\___ \\ \n" + " ____) | | |/ ____ \\ ____) |_| |_ ____) | \n" + " |_____/ |_/_/ \\_\\_____/|_____|_____/ \n" + "\n" #endif - "---------------------------------------------------------------------\n" - " Delivery Generator \n" - " v%s \n" - "---------------------------------------------------------------------\n" + "------------------------------------------------------------------------\n" + " Delivery Generator \n" + " v%s \n" + "------------------------------------------------------------------------\n" "Copyright (C) 2023-2024 %s,\n" "Association of Universities for Research in Astronomy (AURA)\n"; -struct OMC_GLOBAL globals = { +struct STASIS_GLOBAL globals = { .verbose = false, .continue_on_error = false, .always_update_base_environment = false, diff --git a/src/ini.c b/src/ini.c index decd29f..d0757e6 100644 --- a/src/ini.c +++ b/src/ini.c @@ -2,7 +2,7 @@ #include #include #include -#include "omc.h" +#include "core.h" #include "ini.h" struct INIFILE *ini_init() { @@ -114,7 +114,7 @@ struct INIData *ini_getall(struct INIFILE *ini, char *section_name) { int ini_getval(struct INIFILE *ini, char *section_name, char *key, int type, union INIVal *result) { char *token = NULL; - char tbuf[OMC_BUFSIZ]; + char tbuf[STASIS_BUFSIZ]; char *tbufp = tbuf; struct INIData *data; data = ini_data_get(ini, section_name, key); @@ -282,7 +282,7 @@ int ini_write(struct INIFILE *ini, FILE **stream, unsigned mode) { for (size_t x = 0; x < ini->section_count; x++) { fprintf(*stream, "[%s]" LINE_SEP, ini->section[x]->key); for (size_t y = 0; y < ini->section[x]->data_count; y++) { - char outvalue[OMC_BUFSIZ]; + char outvalue[STASIS_BUFSIZ]; memset(outvalue, 0, sizeof(outvalue)); if (ini->section[x]->data[y]->value) { char **parts = split(ini->section[x]->data[y]->value, LINE_SEP, 0); @@ -354,8 +354,8 @@ void ini_free(struct INIFILE **ini) { struct INIFILE *ini_open(const char *filename) { FILE *fp; - char line[OMC_BUFSIZ] = {0}; - char current_section[OMC_BUFSIZ] = {0}; + char line[STASIS_BUFSIZ] = {0}; + char current_section[STASIS_BUFSIZ] = {0}; char reading_value = 0; struct INIFILE *ini = ini_init(); @@ -382,7 +382,7 @@ struct INIFILE *ini_open(const char *filename) { char inikey[2][255]; char *key = inikey[0]; char *key_last = inikey[1]; - char value[OMC_BUFSIZ]; + char value[STASIS_BUFSIZ]; memset(value, 0, sizeof(value)); memset(inikey, 0, sizeof(inikey)); diff --git a/src/omc_indexer.c b/src/omc_indexer.c deleted file mode 100644 index 971f389..0000000 --- a/src/omc_indexer.c +++ /dev/null @@ -1,753 +0,0 @@ -#include -#include -#include "omc.h" - -static struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"destdir", required_argument, 0, 'd'}, - {"verbose", no_argument, 0, 'v'}, - {"unbuffered", no_argument, 0, 'U'}, - {"web", no_argument, 0, 'w'}, - {0, 0, 0, 0}, -}; - -const char *long_options_help[] = { - "Display this usage statement", - "Destination directory", - "Increase output verbosity", - "Disable line buffering", - "Generate HTML indexes (requires pandoc)", - NULL, -}; - -static void usage(char *name) { - int maxopts = sizeof(long_options) / sizeof(long_options[0]); - unsigned char *opts = calloc(maxopts + 1, sizeof(char)); - for (int i = 0; i < maxopts; i++) { - opts[i] = long_options[i].val; - } - printf("usage: %s [-%s] {{OMC_ROOT}...}\n", name, opts); - guard_free(opts); - - for (int i = 0; i < maxopts - 1; i++) { - char line[255]; - sprintf(line, " --%s -%c %-20s", long_options[i].name, long_options[i].val, long_options_help[i]); - puts(line); - } - -} - -int indexer_combine_rootdirs(const char *dest, char **rootdirs, const size_t rootdirs_total) { - char cmd[PATH_MAX]; - - memset(cmd, 0, sizeof(cmd)); - sprintf(cmd, "rsync -ah%s --delete --exclude 'tools/' --exclude 'tmp/' --exclude 'build/' ", globals.verbose ? "v" : "q"); - for (size_t i = 0; i < rootdirs_total; i++) { - sprintf(cmd + strlen(cmd), "'%s'/ ", rootdirs[i]); - } - sprintf(cmd + strlen(cmd), "%s/", dest); - - if (globals.verbose) { - puts(cmd); - } - - if (system(cmd)) { - return -1; - } - return 0; -} - -int indexer_wheels(struct Delivery *ctx) { - return delivery_index_wheel_artifacts(ctx); -} - -int indexer_load_metadata(struct Delivery *ctx, const char *filename) { - char line[OMC_NAME_MAX] = {0}; - FILE *fp; - - fp = fopen(filename, "r"); - if (!fp) { - return -1; - } - - while (fgets(line, sizeof(line) - 1, fp) != NULL) { - char **parts = split(line, " ", 1); - char *name = parts[0]; - char *value = parts[1]; - strip(value); - if (!strcmp(name, "name")) { - ctx->meta.name = strdup(value); - } else if (!strcmp(name, "version")) { - ctx->meta.version = strdup(value); - } else if (!strcmp(name, "rc")) { - ctx->meta.rc = (int) strtol(value, NULL, 10); - } else if (!strcmp(name, "python")) { - ctx->meta.python = strdup(value); - } else if (!strcmp(name, "python_compact")) { - ctx->meta.python_compact = strdup(value); - } else if (!strcmp(name, "mission")) { - ctx->meta.mission = strdup(value); - } else if (!strcmp(name, "codename")) { - ctx->meta.codename = strdup(value); - } else if (!strcmp(name, "platform")) { - ctx->system.platform = calloc(DELIVERY_PLATFORM_MAX, sizeof(*ctx->system.platform)); - char **platform = split(value, " ", 0); - ctx->system.platform[DELIVERY_PLATFORM] = platform[DELIVERY_PLATFORM]; - ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR] = platform[DELIVERY_PLATFORM_CONDA_SUBDIR]; - ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER] = platform[DELIVERY_PLATFORM_CONDA_INSTALLER]; - ctx->system.platform[DELIVERY_PLATFORM_RELEASE] = platform[DELIVERY_PLATFORM_RELEASE]; - } else if (!strcmp(name, "arch")) { - ctx->system.arch = strdup(value); - } else if (!strcmp(name, "time")) { - ctx->info.time_str_epoch = strdup(value); - } else if (!strcmp(name, "release_fmt")) { - ctx->rules.release_fmt = strdup(value); - } else if (!strcmp(name, "release_name")) { - ctx->info.release_name = strdup(value); - } else if (!strcmp(name, "build_name_fmt")) { - ctx->rules.build_name_fmt = strdup(value); - } else if (!strcmp(name, "build_name")) { - ctx->info.build_name = strdup(value); - } else if (!strcmp(name, "build_number_fmt")) { - ctx->rules.build_number_fmt = strdup(value); - } else if (!strcmp(name, "build_number")) { - ctx->info.build_number = strdup(value); - } else if (!strcmp(name, "conda_installer_baseurl")) { - ctx->conda.installer_baseurl = strdup(value); - } else if (!strcmp(name, "conda_installer_name")) { - ctx->conda.installer_name = strdup(value); - } else if (!strcmp(name, "conda_installer_version")) { - ctx->conda.installer_version = strdup(value); - } else if (!strcmp(name, "conda_installer_platform")) { - ctx->conda.installer_platform = strdup(value); - } else if (!strcmp(name, "conda_installer_arch")) { - ctx->conda.installer_arch = strdup(value); - } - GENERIC_ARRAY_FREE(parts); - } - fclose(fp); - - return 0; -} - -int indexer_get_files(struct StrList **out, const char *path, const char *pattern, ...) { - va_list args; - va_start(args, pattern); - char userpattern[PATH_MAX] = {0}; - vsprintf(userpattern, pattern, args); - va_end(args); - struct StrList *list = listdir(path); - if (!list) { - return -1; - } - - if (!(*out)) { - (*out) = strlist_init(); - if (!(*out)) { - guard_strlist_free(&list); - return -1; - } - } - - size_t no_match = 0; - for (size_t i = 0; i < strlist_count(list); i++) { - char *item = strlist_item(list, i); - if (fnmatch(userpattern, item, 0)) { - no_match++; - continue; - } else { - strlist_append(&(*out), item); - } - } - if (no_match >= strlist_count(list)) { - fprintf(stderr, "no files matching the pattern: %s\n", userpattern); - guard_strlist_free(&list); - return -1; - } - guard_strlist_free(&list); - return 0; -} - -int get_latest_rc(struct Delivery ctx[], size_t nelem) { - int result = 0; - for (size_t i = 0; i < nelem; i++) { - if (ctx[i].meta.rc > result) { - result = ctx[i].meta.rc; - } - } - return result; -} - -struct Delivery **get_latest_deliveries(struct Delivery ctx[], size_t nelem) { - struct Delivery **result = NULL; - int latest = 0; - size_t n = 0; - - result = calloc(nelem + 1, sizeof(result)); - if (!result) { - fprintf(stderr, "Unable to allocate %zu bytes for result delivery array: %s\n", nelem * sizeof(result), strerror(errno)); - return NULL; - } - - latest = get_latest_rc(ctx, nelem); - for (size_t i = 0; i < nelem; i++) { - if (ctx[i].meta.rc == latest) { - result[n] = &ctx[i]; - n++; - } - } - - return result; -} - -int micromamba(const char *write_to, const char *prefix, char *command, ...) { - struct utsname sys; - uname(&sys); - - tolower_s(sys.sysname); - if (!strcmp(sys.sysname, "darwin")) { - strcpy(sys.sysname, "osx"); - } - - if (!strcmp(sys.machine, "x86_64")) { - strcpy(sys.machine, "64"); - } - - char url[PATH_MAX]; - sprintf(url, "https://micro.mamba.pm/api/micromamba/%s-%s/latest", sys.sysname, sys.machine); - if (access("latest", F_OK)) { - download(url, "latest", NULL); - } - - char mmbin[PATH_MAX]; - sprintf(mmbin, "%s/micromamba", write_to); - - if (access(mmbin, F_OK)) { - char untarcmd[PATH_MAX]; - mkdirs(write_to, 0755); - sprintf(untarcmd, "tar -xvf latest -C %s --strip-components=1 bin/micromamba 1>/dev/null", write_to); - system(untarcmd); - } - - char cmd[OMC_BUFSIZ]; - memset(cmd, 0, sizeof(cmd)); - sprintf(cmd, "%s -r %s -p %s ", mmbin, prefix, prefix); - va_list args; - va_start(args, command); - vsprintf(cmd + strlen(cmd), command, args); - va_end(args); - - mkdirs(prefix, 0755); - - char rcpath[PATH_MAX]; - sprintf(rcpath, "%s/.condarc", prefix); - touch(rcpath); - - setenv("CONDARC", rcpath, 1); - setenv("MAMBA_ROOT_PREFIX", prefix, 1); - int status = system(cmd); - unsetenv("MAMBA_ROOT_PREFIX"); - - return status; -} - -int indexer_make_website(struct Delivery *ctx) { - char cmd[PATH_MAX]; - char *inputs[] = { - ctx->storage.delivery_dir, "/README.md", - ctx->storage.results_dir, "/README.md", - }; - - if (!find_program("pandoc")) { - return 0; - } - - for (size_t i = 0; i < sizeof(inputs) / sizeof(*inputs); i += 2) { - char fullpath[PATH_MAX]; - memset(fullpath, 0, sizeof(fullpath)); - sprintf(fullpath, "%s/%s", inputs[i], inputs[i + 1]); - if (access(fullpath, F_OK)) { - continue; - } - // Converts a markdown file to html - strcpy(cmd, "pandoc "); - strcat(cmd, fullpath); - strcat(cmd, " "); - strcat(cmd, "-o "); - strcat(cmd, inputs[i]); - strcat(cmd, "/index.html"); - if (globals.verbose) { - puts(cmd); - } - // This might be negative when killed by a signal. - // Otherwise, the return code is not critical to us. - if (system(cmd) < 0) { - return 1; - } - } - - return 0; -} - -int indexer_conda(struct Delivery *ctx) { - int status = 0; - char prefix[PATH_MAX]; - sprintf(prefix, "%s/%s", ctx->storage.tmpdir, "indexer"); - - status += micromamba(ctx->storage.tmpdir, prefix, "config prepend --env channels conda-forge"); - if (!globals.verbose) { - status += micromamba(ctx->storage.tmpdir, prefix, "config set --env quiet true"); - } - status += micromamba(ctx->storage.tmpdir, prefix, "config set --env always_yes true"); - status += micromamba(ctx->storage.tmpdir, prefix, "install conda-build"); - status += micromamba(ctx->storage.tmpdir, prefix, "run conda index %s", ctx->storage.conda_artifact_dir); - return status; -} - -static struct StrList *get_architectures(struct Delivery ctx[], size_t nelem) { - struct StrList *architectures = strlist_init(); - for (size_t i = 0; i < nelem; i++) { - if (!strstr_array(architectures->data, ctx[i].system.arch)) { - strlist_append(&architectures, ctx[i].system.arch); - } - } - return architectures; -} - -static struct StrList *get_platforms(struct Delivery ctx[], size_t nelem) { - struct StrList *platforms = strlist_init(); - for (size_t i = 0; i < nelem; i++) { - if (!strstr_array(platforms->data, ctx[i].system.platform[DELIVERY_PLATFORM_RELEASE])) { - strlist_append(&platforms, ctx[i].system.platform[DELIVERY_PLATFORM_RELEASE]); - } - } - return platforms; -} - -int indexer_symlinks(struct Delivery ctx[], size_t nelem) { - struct Delivery **data = NULL; - data = get_latest_deliveries(ctx, nelem); - //int latest = get_latest_rc(ctx, nelem); - - if (!pushd(ctx->storage.delivery_dir)) { - for (size_t i = 0; i < nelem; i++) { - char link_name_spec[PATH_MAX]; - char link_name_readme[PATH_MAX]; - - char file_name_spec[PATH_MAX]; - char file_name_readme[PATH_MAX]; - - if (!data[i]) { - continue; - } - sprintf(link_name_spec, "latest-py%s-%s-%s.yml", data[i]->meta.python_compact, data[i]->system.platform[DELIVERY_PLATFORM_RELEASE], data[i]->system.arch); - sprintf(file_name_spec, "%s.yml", data[i]->info.release_name); - - sprintf(link_name_readme, "README-py%s-%s-%s.md", data[i]->meta.python_compact, data[i]->system.platform[DELIVERY_PLATFORM_RELEASE], data[i]->system.arch); - sprintf(file_name_readme, "README-%s.md", data[i]->info.release_name); - - if (!access(link_name_spec, F_OK)) { - if (unlink(link_name_spec)) { - fprintf(stderr, "Unable to remove spec link: %s\n", link_name_spec); - } - } - if (!access(link_name_readme, F_OK)) { - if (unlink(link_name_readme)) { - fprintf(stderr, "Unable to remove readme link: %s\n", link_name_readme); - } - } - - if (globals.verbose) { - printf("%s -> %s\n", file_name_spec, link_name_spec); - } - if (symlink(file_name_spec, link_name_spec)) { - fprintf(stderr, "Unable to link %s as %s\n", file_name_spec, link_name_spec); - } - - if (globals.verbose) { - printf("%s -> %s\n", file_name_readme, link_name_readme); - } - if (symlink(file_name_readme, link_name_readme)) { - fprintf(stderr, "Unable to link %s as %s\n", file_name_readme, link_name_readme); - } - } - popd(); - } else { - fprintf(stderr, "Unable to enter delivery directory: %s\n", ctx->storage.delivery_dir); - guard_free(data); - return -1; - } - - // "latest" is an array of pointers to ctx[]. Do not free the contents of the array. - guard_free(data); - return 0; -} - -int indexer_readmes(struct Delivery ctx[], size_t nelem) { - struct Delivery **latest = NULL; - latest = get_latest_deliveries(ctx, nelem); - - char indexfile[PATH_MAX] = {0}; - sprintf(indexfile, "%s/README.md", ctx->storage.delivery_dir); - - if (!pushd(ctx->storage.delivery_dir)) { - FILE *indexfp; - indexfp = fopen(indexfile, "w+"); - if (!indexfp) { - fprintf(stderr, "Unable to open %s for writing\n", indexfile); - return -1; - } - struct StrList *archs = get_architectures(*latest, nelem); - struct StrList *platforms = get_platforms(*latest, nelem); - - fprintf(indexfp, "# %s-%s\n\n", ctx->meta.name, ctx->meta.version); - fprintf(indexfp, "## Current Release\n\n"); - for (size_t p = 0; p < strlist_count(platforms); p++) { - char *platform = strlist_item(platforms, p); - for (size_t a = 0; a < strlist_count(archs); a++) { - char *arch = strlist_item(archs, a); - int have_combo = 0; - for (size_t i = 0; i < nelem; i++) { - if (strstr(latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], platform) && strstr(latest[i]->system.arch, arch)) { - have_combo = 1; - } - } - if (!have_combo) { - continue; - } - fprintf(indexfp, "### %s-%s\n\n", platform, arch); - - fprintf(indexfp, "|Release|Info|Receipt|\n"); - fprintf(indexfp, "|:----:|:----:|:----:|\n"); - for (size_t i = 0; i < nelem; i++) { - char link_name[PATH_MAX]; - char readme_name[PATH_MAX]; - char conf_name[PATH_MAX]; - char conf_name_relative[PATH_MAX]; - if (!latest[i]) { - continue; - } - sprintf(link_name, "latest-py%s-%s-%s.yml", latest[i]->meta.python_compact, latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], latest[i]->system.arch); - sprintf(readme_name, "README-py%s-%s-%s.md", latest[i]->meta.python_compact, latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], latest[i]->system.arch); - sprintf(conf_name, "%s.ini", latest[i]->info.release_name); - sprintf(conf_name_relative, "../config/%s-rendered.ini", latest[i]->info.release_name); - if (strstr(link_name, platform) && strstr(link_name, arch)) { - fprintf(indexfp, "|[%s](%s)|[%s](%s)|[%s](%s)|\n", link_name, link_name, readme_name, readme_name, conf_name, conf_name_relative); - } - } - fprintf(indexfp, "\n"); - } - fprintf(indexfp, "\n"); - } - guard_strlist_free(&archs); - guard_strlist_free(&platforms); - fclose(indexfp); - popd(); - } else { - fprintf(stderr, "Unable to enter delivery directory: %s\n", ctx->storage.delivery_dir); - guard_free(latest); - return -1; - } - - // "latest" is an array of pointers to ctxs[]. Do not free the contents of the array. - guard_free(latest); - return 0; -} - -int indexer_junitxml_report(struct Delivery ctx[], size_t nelem) { - struct Delivery **latest = NULL; - latest = get_latest_deliveries(ctx, nelem); - - char indexfile[PATH_MAX] = {0}; - sprintf(indexfile, "%s/README.md", ctx->storage.results_dir); - - struct StrList *file_listing = listdir(ctx->storage.results_dir); - if (!file_listing) { - // no test results to process - return 0; - } - - if (!pushd(ctx->storage.results_dir)) { - FILE *indexfp; - indexfp = fopen(indexfile, "w+"); - if (!indexfp) { - fprintf(stderr, "Unable to open %s for writing\n", indexfile); - return -1; - } - struct StrList *archs = get_architectures(*latest, nelem); - struct StrList *platforms = get_platforms(*latest, nelem); - - fprintf(indexfp, "# %s-%s Test Report\n\n", ctx->meta.name, ctx->meta.version); - fprintf(indexfp, "## Current Release\n\n"); - for (size_t p = 0; p < strlist_count(platforms); p++) { - char *platform = strlist_item(platforms, p); - for (size_t a = 0; a < strlist_count(archs); a++) { - char *arch = strlist_item(archs, a); - int have_combo = 0; - for (size_t i = 0; i < nelem; i++) { - if (strstr(latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], platform) && strstr(latest[i]->system.arch, arch)) { - have_combo = 1; - break; - } - } - if (!have_combo) { - continue; - } - fprintf(indexfp, "### %s-%s\n\n", platform, arch); - - fprintf(indexfp, "|Suite|Duration|Fail |Skip |Error |\n"); - fprintf(indexfp, "|:----|:------:|:------:|:---:|:----:|\n"); - for (size_t f = 0; f < strlist_count(file_listing); f++) { - char *filename = strlist_item(file_listing, f); - if (!endswith(filename, ".xml")) { - continue; - } - - if (strstr(filename, platform) && strstr(filename, arch)) { - struct JUNIT_Testsuite *testsuite = junitxml_testsuite_read(filename); - if (testsuite) { - if (globals.verbose) { - printf("%s: duration: %0.4f, failed: %d, skipped: %d, errors: %d\n", filename, testsuite->time, testsuite->failures, testsuite->skipped, testsuite->errors); - } - fprintf(indexfp, "|[%s](%s)|%0.4f|%d|%d|%d|\n", filename, filename, testsuite->time, testsuite->failures, testsuite->skipped, testsuite->errors); - /* - * TODO: Display failure/skip/error output. - * - for (size_t i = 0; i < testsuite->_tc_inuse; i++) { - if (testsuite->testcase[i]->tc_result_state_type) { - printf("testcase: %s :: %s\n", testsuite->testcase[i]->classname, testsuite->testcase[i]->name); - if (testsuite->testcase[i]->tc_result_state_type == JUNIT_RESULT_STATE_FAILURE) { - printf("failure: %s\n", testsuite->testcase[i]->result_state.failure->message); - } else if (testsuite->testcase[i]->tc_result_state_type == JUNIT_RESULT_STATE_SKIPPED) { - printf("skipped: %s\n", testsuite->testcase[i]->result_state.skipped->message); - } - } - } - */ - junitxml_testsuite_free(&testsuite); - } else { - fprintf(stderr, "bad test suite: %s: %s\n", strerror(errno), filename); - continue; - } - } - } - fprintf(indexfp, "\n"); - } - fprintf(indexfp, "\n"); - } - guard_strlist_free(&archs); - guard_strlist_free(&platforms); - fclose(indexfp); - popd(); - } else { - fprintf(stderr, "Unable to enter delivery directory: %s\n", ctx->storage.delivery_dir); - guard_free(latest); - return -1; - } - - // "latest" is an array of pointers to ctxs[]. Do not free the contents of the array. - guard_free(latest); - return 0; -} - -void indexer_init_dirs(struct Delivery *ctx, const char *workdir) { - path_store(&ctx->storage.root, PATH_MAX, workdir, ""); - path_store(&ctx->storage.tmpdir, PATH_MAX, ctx->storage.root, "tmp"); - if (delivery_init_tmpdir(ctx)) { - fprintf(stderr, "Failed to configure temporary storage directory\n"); - exit(1); - } - path_store(&ctx->storage.output_dir, PATH_MAX, ctx->storage.root, "output"); - path_store(&ctx->storage.cfgdump_dir, PATH_MAX, ctx->storage.output_dir, "config"); - path_store(&ctx->storage.meta_dir, PATH_MAX, ctx->storage.output_dir, "meta"); - path_store(&ctx->storage.delivery_dir, PATH_MAX, ctx->storage.output_dir, "delivery"); - path_store(&ctx->storage.package_dir, PATH_MAX, ctx->storage.output_dir, "packages"); - path_store(&ctx->storage.results_dir, PATH_MAX, ctx->storage.output_dir, "results"); - path_store(&ctx->storage.wheel_artifact_dir, PATH_MAX, ctx->storage.package_dir, "wheels"); - path_store(&ctx->storage.conda_artifact_dir, PATH_MAX, ctx->storage.package_dir, "conda"); -} - -int main(int argc, char *argv[]) { - size_t rootdirs_total = 0; - char *destdir = NULL; - char **rootdirs = NULL; - int do_html = 0; - int c = 0; - int option_index = 0; - while ((c = getopt_long(argc, argv, "hd:vUw", long_options, &option_index)) != -1) { - switch (c) { - case 'h': - usage(path_basename(argv[0])); - exit(0); - case 'd': - destdir = strdup(optarg); - break; - case 'U': - fflush(stdout); - fflush(stderr); - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); - break; - case 'v': - globals.verbose = 1; - break; - case 'w': - do_html = 1; - break; - case '?': - default: - exit(1); - } - } - - int current_index = optind; - if (optind < argc) { - rootdirs_total = argc - current_index; - while (optind < argc) { - // use first positional argument - rootdirs = &argv[optind++]; - break; - } - } - - if (isempty(destdir)) { - destdir = strdup("output"); - } - - if (!rootdirs || !rootdirs_total) { - fprintf(stderr, "You must specify at least one OMC root directory to index\n"); - exit(1); - } else { - for (size_t i = 0; i < rootdirs_total; i++) { - if (isempty(rootdirs[i]) || !strcmp(rootdirs[i], "/") || !strcmp(rootdirs[i], "\\")) { - SYSERROR("Unsafe directory: %s", rootdirs[i]); - exit(1); - } - } - } - - char *workdir; - char workdir_template[PATH_MAX]; - char *system_tmp = getenv("TMPDIR"); - if (system_tmp) { - strcat(workdir_template, system_tmp); - } else { - strcat(workdir_template, "/tmp"); - } - strcat(workdir_template, "/omc-combine.XXXXXX"); - workdir = mkdtemp(workdir_template); - if (!workdir) { - SYSERROR("Unable to create temporary directory: %s", workdir_template); - exit(1); - } else if (isempty(workdir) || !strcmp(workdir, "/") || !strcmp(workdir, "\\")) { - SYSERROR("Unsafe directory: %s", workdir); - exit(1); - } - - struct Delivery ctx; - memset(&ctx, 0, sizeof(ctx)); - - printf(BANNER, VERSION, AUTHOR); - - indexer_init_dirs(&ctx, workdir); - - msg(OMC_MSG_L1, "%s delivery root %s\n", - rootdirs_total > 1 ? "Merging" : "Indexing", - rootdirs_total > 1 ? "directories" : "directory"); - if (indexer_combine_rootdirs(workdir, rootdirs, rootdirs_total)) { - SYSERROR("%s", "Copy operation failed"); - rmtree(workdir); - exit(1); - } - - if (access(ctx.storage.conda_artifact_dir, F_OK)) { - mkdirs(ctx.storage.conda_artifact_dir, 0755); - } - - if (access(ctx.storage.wheel_artifact_dir, F_OK)) { - mkdirs(ctx.storage.wheel_artifact_dir, 0755); - } - - msg(OMC_MSG_L1, "Indexing conda packages\n"); - if (indexer_conda(&ctx)) { - SYSERROR("%s", "Conda package indexing operation failed"); - exit(1); - } - - msg(OMC_MSG_L1, "Indexing wheel packages\n"); - if (indexer_wheels(&ctx)) { - SYSERROR("%s", "Python package indexing operation failed"); - exit(1); - } - - msg(OMC_MSG_L1, "Loading metadata\n"); - struct StrList *metafiles = NULL; - indexer_get_files(&metafiles, ctx.storage.meta_dir, "*.omc"); - strlist_sort(metafiles, OMC_SORT_LEN_ASCENDING); - struct Delivery local[strlist_count(metafiles)]; - - for (size_t i = 0; i < strlist_count(metafiles); i++) { - char *item = strlist_item(metafiles, i); - memset(&local[i], 0, sizeof(ctx)); - memcpy(&local[i], &ctx, sizeof(ctx)); - char path[PATH_MAX]; - sprintf(path, "%s/%s", ctx.storage.meta_dir, item); - if (globals.verbose) { - puts(path); - } - indexer_load_metadata(&local[i], path); - } - - msg(OMC_MSG_L1, "Generating links to latest release iteration\n"); - if (indexer_symlinks(local, strlist_count(metafiles))) { - SYSERROR("%s", "Link generation failed"); - exit(1); - } - - msg(OMC_MSG_L1, "Generating README.md\n"); - if (indexer_readmes(local, strlist_count(metafiles))) { - SYSERROR("%s", "README indexing operation failed"); - exit(1); - } - - msg(OMC_MSG_L1, "Indexing test results\n"); - if (indexer_junitxml_report(local, strlist_count(metafiles))) { - SYSERROR("%s", "Test result indexing operation failed"); - exit(1); - } - - if (do_html) { - msg(OMC_MSG_L1, "Generating HTML indexes\n"); - if (indexer_make_website(local)) { - SYSERROR("%s", "Site creation failed"); - exit(1); - } - } - - msg(OMC_MSG_L1, "Copying indexed delivery to '%s'\n", destdir); - char cmd[PATH_MAX]; - memset(cmd, 0, sizeof(cmd)); - sprintf(cmd, "rsync -ah%s --delete --exclude 'tmp/' --exclude 'tools/' '%s/' '%s/'", globals.verbose ? "v" : "q", workdir, destdir); - guard_free(destdir); - - if (globals.verbose) { - puts(cmd); - } - - if (system(cmd)) { - SYSERROR("%s", "Copy operation failed"); - rmtree(workdir); - exit(1); - } - - msg(OMC_MSG_L1, "Removing work directory: %s\n", workdir); - if (rmtree(workdir)) { - SYSERROR("Failed to remove work directory: %s", strerror(errno)); - } - - guard_strlist_free(&metafiles); - delivery_free(&ctx); - globals_free(); - msg(OMC_MSG_L1, "Done!\n"); - return 0; -} diff --git a/src/omc_main.c b/src/omc_main.c deleted file mode 100644 index 90460ee..0000000 --- a/src/omc_main.c +++ /dev/null @@ -1,612 +0,0 @@ -#include -#include -#include -#include -#include -#include "omc.h" - -#define OPT_ALWAYS_UPDATE_BASE 1000 -#define OPT_NO_DOCKER 1001 -#define OPT_NO_ARTIFACTORY 1002 -#define OPT_NO_TESTING 1003 -static struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'V'}, - {"continue-on-error", no_argument, 0, 'C'}, - {"config", required_argument, 0, 'c'}, - {"python", required_argument, 0, 'p'}, - {"verbose", no_argument, 0, 'v'}, - {"unbuffered", no_argument, 0, 'U'}, - {"update-base", no_argument, 0, OPT_ALWAYS_UPDATE_BASE}, - {"no-docker", no_argument, 0, OPT_NO_DOCKER}, - {"no-artifactory", no_argument, 0, OPT_NO_ARTIFACTORY}, - {"no-testing", no_argument, 0, OPT_NO_TESTING}, - {0, 0, 0, 0}, -}; - -const char *long_options_help[] = { - "Display this usage statement", - "Display program version", - "Allow tests to fail", - "Read configuration file", - "Override version of Python in configuration", - "Increase output verbosity", - "Disable line buffering", - "Update conda installation prior to OMC environment creation", - "Do not build docker images", - "Do not upload artifacts to Artifactory", - "Do not execute test scripts", - NULL, -}; - -static int get_option_max_width(struct option option[]) { - int i = 0; - int max = 0; - const int indent = 4; - while (option[i].name != 0) { - int len = (int) strlen(option[i].name); - if (option[i].has_arg) { - len += indent; - } - if (len > max) { - max = len; - } - i++; - } - return max; -} - -static void usage(char *progname) { - printf("usage: %s ", progname); - printf("[-"); - for (int x = 0; long_options[x].val != 0; x++) { - if (long_options[x].has_arg == no_argument && long_options[x].val <= 'z') { - putchar(long_options[x].val); - } - } - printf("] {DELIVERY_FILE}\n"); - - int width = get_option_max_width(long_options); - for (int x = 0; long_options[x].name != 0; x++) { - char tmp[OMC_NAME_MAX] = {0}; - char output[sizeof(tmp)] = {0}; - char opt_long[50] = {0}; // --? [ARG]? - char opt_short[50] = {0}; // -? [ARG]? - - strcat(opt_long, "--"); - strcat(opt_long, long_options[x].name); - if (long_options[x].has_arg) { - strcat(opt_long, " ARG"); - } - - if (long_options[x].val <= 'z') { - strcat(opt_short, "-"); - opt_short[1] = (char) long_options[x].val; - if (long_options[x].has_arg) { - strcat(opt_short, " ARG"); - } - } else { - strcat(opt_short, " "); - } - - sprintf(tmp, " %%-%ds\t%%s\t\t%%s", width + 4); - sprintf(output, tmp, opt_long, opt_short, long_options_help[x]); - puts(output); - } -} - - - -static void check_system_requirements(struct Delivery *ctx) { - const char *tools_required[] = { - "rsync", - NULL, - }; - - msg(OMC_MSG_L1, "Checking system requirements\n"); - for (size_t i = 0; tools_required[i] != NULL; i++) { - if (!find_program(tools_required[i])) { - msg(OMC_MSG_L2 | OMC_MSG_ERROR, "'%s' must be installed.\n", tools_required[i]); - exit(1); - } - } - - if (!globals.tmpdir && !ctx->storage.tmpdir) { - delivery_init_tmpdir(ctx); - } - - struct DockerCapabilities dcap; - if (!docker_capable(&dcap)) { - msg(OMC_MSG_L2 | OMC_MSG_WARN, "Docker is broken\n"); - msg(OMC_MSG_L3, "Available: %s\n", dcap.available ? "Yes" : "No"); - msg(OMC_MSG_L3, "Usable: %s\n", dcap.usable ? "Yes" : "No"); - msg(OMC_MSG_L3, "Podman [Docker Emulation]: %s\n", dcap.podman ? "Yes" : "No"); - msg(OMC_MSG_L3, "Build plugin(s): "); - if (dcap.usable) { - if (dcap.build & OMC_DOCKER_BUILD) { - printf("build "); - } - if (dcap.build & OMC_DOCKER_BUILD_X) { - printf("buildx "); - } - puts(""); - } else { - printf("N/A\n"); - } - - // disable docker builds - globals.enable_docker = false; - } -} - -int main(int argc, char *argv[]) { - struct Delivery ctx; - struct Process proc = { - .f_stdout = "", - .f_stderr = "", - .redirect_stderr = 0, - }; - char env_name[OMC_NAME_MAX] = {0}; - char env_name_testing[OMC_NAME_MAX] = {0}; - char *delivery_input = NULL; - char *config_input = NULL; - char installer_url[PATH_MAX]; - char python_override_version[OMC_NAME_MAX]; - int user_disabled_docker = false; - - memset(env_name, 0, sizeof(env_name)); - memset(env_name_testing, 0, sizeof(env_name_testing)); - memset(installer_url, 0, sizeof(installer_url)); - memset(python_override_version, 0, sizeof(python_override_version)); - memset(&proc, 0, sizeof(proc)); - memset(&ctx, 0, sizeof(ctx)); - - int c; - int option_index = 0; - while ((c = getopt_long(argc, argv, "hVCc:p:vU", long_options, &option_index)) != -1) { - switch (c) { - case 'h': - usage(path_basename(argv[0])); - exit(0); - case 'V': - puts(VERSION); - exit(0); - case 'c': - config_input = strdup(optarg); - break; - case 'C': - globals.continue_on_error = true; - break; - case 'p': - strcpy(python_override_version, optarg); - break; - case OPT_ALWAYS_UPDATE_BASE: - globals.always_update_base_environment = true; - break; - case 'U': - setenv("PYTHONUNBUFFERED", "1", 1); - fflush(stdout); - fflush(stderr); - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); - break; - case 'v': - globals.verbose = true; - break; - case OPT_NO_DOCKER: - globals.enable_docker = false; - user_disabled_docker = true; - break; - case OPT_NO_ARTIFACTORY: - globals.enable_artifactory = false; - break; - case OPT_NO_TESTING: - globals.enable_testing = false; - break; - case '?': - default: - exit(1); - } - } - - if (optind < argc) { - while (optind < argc) { - // use first positional argument - delivery_input = argv[optind++]; - break; - } - } - - if (!delivery_input) { - fprintf(stderr, "error: a DELIVERY_FILE is required\n"); - usage(path_basename(argv[0])); - exit(1); - } - - printf(BANNER, VERSION, AUTHOR); - - msg(OMC_MSG_L1, "Setup\n"); - - // Expose variables for use with the template engine - // NOTE: These pointers are populated by delivery_init() so please avoid using - // tpl_render() until then. - tpl_register("meta.name", &ctx.meta.name); - tpl_register("meta.version", &ctx.meta.version); - tpl_register("meta.codename", &ctx.meta.codename); - tpl_register("meta.mission", &ctx.meta.mission); - tpl_register("meta.python", &ctx.meta.python); - tpl_register("meta.python_compact", &ctx.meta.python_compact); - tpl_register("info.time_str_epoch", &ctx.info.time_str_epoch); - tpl_register("info.release_name", &ctx.info.release_name); - tpl_register("info.build_name", &ctx.info.build_name); - tpl_register("info.build_number", &ctx.info.build_number); - tpl_register("storage.tmpdir", &ctx.storage.tmpdir); - tpl_register("storage.output_dir", &ctx.storage.output_dir); - tpl_register("storage.delivery_dir", &ctx.storage.delivery_dir); - tpl_register("storage.conda_artifact_dir", &ctx.storage.conda_artifact_dir); - tpl_register("storage.wheel_artifact_dir", &ctx.storage.wheel_artifact_dir); - tpl_register("storage.build_sources_dir", &ctx.storage.build_sources_dir); - tpl_register("storage.build_docker_dir", &ctx.storage.build_docker_dir); - tpl_register("storage.results_dir", &ctx.storage.results_dir); - tpl_register("conda.installer_baseurl", &ctx.conda.installer_baseurl); - tpl_register("conda.installer_name", &ctx.conda.installer_name); - tpl_register("conda.installer_version", &ctx.conda.installer_version); - tpl_register("conda.installer_arch", &ctx.conda.installer_arch); - tpl_register("conda.installer_platform", &ctx.conda.installer_platform); - tpl_register("deploy.jfrog.repo", &globals.jfrog.repo); - tpl_register("deploy.jfrog.url", &globals.jfrog.url); - tpl_register("deploy.docker.registry", &ctx.deploy.docker.registry); - tpl_register("workaround.tox_posargs", &globals.workaround.tox_posargs); - - // Set up PREFIX/etc directory information - // The user may manipulate the base directory path with OMC_SYSCONFDIR - // environment variable - char omc_sysconfdir_tmp[PATH_MAX]; - if (getenv("OMC_SYSCONFDIR")) { - strncpy(omc_sysconfdir_tmp, getenv("OMC_SYSCONFDIR"), sizeof(omc_sysconfdir_tmp) - 1); - } else { - strncpy(omc_sysconfdir_tmp, OMC_SYSCONFDIR, sizeof(omc_sysconfdir_tmp) - 1); - } - - globals.sysconfdir = realpath(omc_sysconfdir_tmp, NULL); - if (!globals.sysconfdir) { - msg(OMC_MSG_ERROR | OMC_MSG_L1, "Unable to resolve path to configuration directory: %s\n", omc_sysconfdir_tmp); - exit(1); - } - - // Override Python version from command-line, if any - if (strlen(python_override_version)) { - guard_free(ctx.meta.python); - ctx.meta.python = strdup(python_override_version); - guard_free(ctx.meta.python_compact); - ctx.meta.python_compact = to_short_version(ctx.meta.python); - } - - if (!config_input) { - // no configuration passed by argument. use basic config. - char cfgfile[PATH_MAX * 2]; - sprintf(cfgfile, "%s/%s", globals.sysconfdir, "omc.ini"); - if (!access(cfgfile, F_OK | R_OK)) { - config_input = strdup(cfgfile); - } else { - msg(OMC_MSG_WARN, "OMC global configuration is not readable, or does not exist: %s", cfgfile); - } - } - - if (config_input) { - msg(OMC_MSG_L2, "Reading OMC global configuration: %s\n", config_input); - ctx._omc_ini_fp.cfg = ini_open(config_input); - if (!ctx._omc_ini_fp.cfg) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Failed to read config file: %s, %s\n", delivery_input, strerror(errno)); - exit(1); - } - ctx._omc_ini_fp.cfg_path = strdup(config_input); - guard_free(config_input); - } - - msg(OMC_MSG_L2, "Reading OMC delivery configuration: %s\n", delivery_input); - ctx._omc_ini_fp.delivery = ini_open(delivery_input); - if (!ctx._omc_ini_fp.delivery) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Failed to read delivery file: %s, %s\n", delivery_input, strerror(errno)); - exit(1); - } - ctx._omc_ini_fp.delivery_path = strdup(delivery_input); - - msg(OMC_MSG_L2, "Bootstrapping delivery context\n"); - if (bootstrap_build_info(&ctx)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Failed to bootstrap delivery context\n"); - exit(1); - } - - msg(OMC_MSG_L2, "Initializing delivery context\n"); - if (delivery_init(&ctx)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Failed to initialize delivery context\n"); - exit(1); - } - check_system_requirements(&ctx); - - msg(OMC_MSG_L2, "Configuring JFrog CLI\n"); - if (delivery_init_artifactory(&ctx)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "JFrog CLI configuration failed\n"); - exit(1); - } - - runtime_apply(ctx.runtime.environ); - strcpy(env_name, ctx.info.release_name); - strcpy(env_name_testing, env_name); - strcat(env_name_testing, "-test"); - - msg(OMC_MSG_L1, "Overview\n"); - delivery_meta_show(&ctx); - delivery_conda_show(&ctx); - delivery_tests_show(&ctx); - if (globals.verbose) { - //delivery_runtime_show(&ctx); - } - - msg(OMC_MSG_L1, "Conda setup\n"); - delivery_get_installer_url(&ctx, installer_url); - msg(OMC_MSG_L2, "Downloading: %s\n", installer_url); - if (delivery_get_installer(&ctx, installer_url)) { - msg(OMC_MSG_ERROR, "download failed: %s\n", installer_url); - exit(1); - } - - // Unlikely to occur: this should help prevent rmtree() from destroying your entire filesystem - // if path is "/" then, die - // or if empty string, die - if (!strcmp(ctx.storage.conda_install_prefix, DIR_SEP) || !strlen(ctx.storage.conda_install_prefix)) { - fprintf(stderr, "error: ctx.storage.conda_install_prefix is malformed!\n"); - exit(1); - } - - msg(OMC_MSG_L2, "Installing: %s\n", ctx.conda.installer_name); - delivery_install_conda(ctx.conda.installer_path, ctx.storage.conda_install_prefix); - - msg(OMC_MSG_L2, "Configuring: %s\n", ctx.storage.conda_install_prefix); - delivery_conda_enable(&ctx, ctx.storage.conda_install_prefix); - - char *pathvar = NULL; - pathvar = getenv("PATH"); - if (!pathvar) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "PATH variable is not set. Cannot continue.\n"); - exit(1); - } else { - char pathvar_tmp[OMC_BUFSIZ]; - sprintf(pathvar_tmp, "%s/bin:%s", ctx.storage.conda_install_prefix, pathvar); - setenv("PATH", pathvar_tmp, 1); - pathvar = NULL; - } - - msg(OMC_MSG_L1, "Creating release environment(s)\n"); - if (ctx.meta.based_on && strlen(ctx.meta.based_on)) { - if (conda_env_remove(env_name)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "failed to remove release environment: %s\n", env_name_testing); - exit(1); - } - msg(OMC_MSG_L2, "Based on release: %s\n", ctx.meta.based_on); - if (conda_env_create_from_uri(env_name, ctx.meta.based_on)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "unable to install release environment using configuration file\n"); - exit(1); - } - - if (conda_env_remove(env_name_testing)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "failed to remove testing environment\n"); - exit(1); - } - if (conda_env_create_from_uri(env_name_testing, ctx.meta.based_on)) { - msg(OMC_MSG_ERROR | OMC_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(OMC_MSG_ERROR | OMC_MSG_L2, "failed to create release environment\n"); - exit(1); - } - if (conda_env_create(env_name_testing, ctx.meta.python, NULL)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "failed to create release environment\n"); - exit(1); - } - } - - // Activate test environment - msg(OMC_MSG_L1, "Activating test environment\n"); - if (conda_activate(ctx.storage.conda_install_prefix, env_name_testing)) { - fprintf(stderr, "failed to activate test environment\n"); - exit(1); - } - - delivery_gather_tool_versions(&ctx); - if (!ctx.conda.tool_version) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Could not determine conda version\n"); - exit(1); - } - if (!ctx.conda.tool_build_version) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "Could not determine conda-build version\n"); - exit(1); - } - - if (pip_exec("install build")) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "'build' tool installation failed"); - exit(1); - } - - // Execute configuration-defined tests - if (globals.enable_testing) { - msg(OMC_MSG_L1, "Begin test execution\n"); - delivery_tests_run(&ctx); - msg(OMC_MSG_L1, "Rewriting test results\n"); - delivery_fixup_test_results(&ctx); - } else { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Test execution is disabled\n"); - } - - msg(OMC_MSG_L1, "Generating deferred package listing\n"); - // Test succeeded so move on to producing package artifacts - delivery_defer_packages(&ctx, DEFER_CONDA); - delivery_defer_packages(&ctx, DEFER_PIP); - - if (ctx.conda.conda_packages_defer && strlist_count(ctx.conda.conda_packages_defer)) { - msg(OMC_MSG_L2, "Building Conda recipe(s)\n"); - if (delivery_build_recipes(&ctx)) { - exit(1); - } - msg(OMC_MSG_L3, "Copying artifacts\n"); - if (delivery_copy_conda_artifacts(&ctx)) { - exit(1); - } - msg(OMC_MSG_L3, "Indexing artifacts\n"); - if (delivery_index_conda_artifacts(&ctx)) { - exit(1); - } - } - - if (strlist_count(ctx.conda.pip_packages_defer)) { - if (!(ctx.conda.wheels_packages = delivery_build_wheels(&ctx))) { - exit(1); - } - if (delivery_index_wheel_artifacts(&ctx)) { - exit(1); - } - - } - - // Populate the release environment - msg(OMC_MSG_L1, "Populating release environment\n"); - msg(OMC_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(OMC_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(OMC_MSG_L3, "No deferred conda packages\n"); - } - - msg(OMC_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(OMC_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(OMC_MSG_L3, "No deferred pip packages\n"); - } - - conda_exec("list"); - - msg(OMC_MSG_L1, "Creating release\n"); - msg(OMC_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(OMC_MSG_ERROR | OMC_MSG_L2, "failed %s\n", filename); - exit(1); - } - ini_write(ctx._omc_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(OMC_MSG_ERROR | OMC_MSG_L2, "failed %s\n", filename); - exit(1); - } - ini_write(ctx._omc_ini_fp.delivery, &spec, INI_WRITE_PRESERVE); - fclose(spec); - popd(); - } else { - SYSERROR("Failed to enter directory: %s", ctx.storage.delivery_dir); - exit(1); - } - - msg(OMC_MSG_L2, "Exporting %s\n", env_name_testing); - if (conda_env_export(env_name_testing, ctx.storage.delivery_dir, env_name_testing)) { - msg(OMC_MSG_ERROR | OMC_MSG_L2, "failed %s\n", env_name_testing); - exit(1); - } - - msg(OMC_MSG_L2, "Exporting %s\n", env_name); - if (conda_env_export(env_name, ctx.storage.delivery_dir, env_name)) { - msg(OMC_MSG_ERROR | OMC_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.) - char specfile[PATH_MAX]; - sprintf(specfile, "%s/%s.yml", ctx.storage.delivery_dir, env_name); - msg(OMC_MSG_L3, "Rewriting release spec file (stage 1): %s\n", path_basename(specfile)); - delivery_rewrite_spec(&ctx, specfile, DELIVERY_REWRITE_SPEC_STAGE_1); - - msg(OMC_MSG_L1, "Rendering mission templates\n"); - delivery_mission_render_files(&ctx); - - int want_docker = ini_section_search(&ctx._omc_ini_fp.delivery, INI_SEARCH_BEGINS, "deploy:docker") ? true : false; - int want_artifactory = ini_section_search(&ctx._omc_ini_fp.delivery, INI_SEARCH_BEGINS, "deploy:artifactory") ? true : false; - - if (want_docker) { - if (user_disabled_docker) { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Docker image building is disabled by CLI argument\n"); - } else { - char dockerfile[PATH_MAX] = {0}; - sprintf(dockerfile, "%s/%s", ctx.storage.build_docker_dir, "Dockerfile"); - if (globals.enable_docker) { - if (!access(dockerfile, F_OK)) { - msg(OMC_MSG_L1, "Building Docker image\n"); - if (delivery_docker(&ctx)) { - msg(OMC_MSG_L1 | OMC_MSG_ERROR, "Failed to build docker image!\n"); - COE_CHECK_ABORT(1, "Failed to build docker image"); - } - } else { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Docker image building is disabled. No Dockerfile found in %s\n", ctx.storage.build_docker_dir); - } - } else { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Docker image building is disabled. System configuration error\n"); - } - } - } else { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Docker image building is disabled. deploy:docker is not configured\n"); - } - - msg(OMC_MSG_L3, "Rewriting release spec file (stage 2): %s\n", path_basename(specfile)); - delivery_rewrite_spec(&ctx, specfile, DELIVERY_REWRITE_SPEC_STAGE_2); - - msg(OMC_MSG_L1, "Dumping metadata\n"); - if (delivery_dump_metadata(&ctx)) { - msg(OMC_MSG_L1 | OMC_MSG_ERROR, "Metadata dump failed\n"); - } - - if (want_artifactory) { - if (globals.enable_artifactory) { - msg(OMC_MSG_L1, "Uploading artifacts\n"); - delivery_artifact_upload(&ctx); - } else { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Artifact uploading is disabled\n"); - } - } else { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "Artifact uploading is disabled. deploy:artifactory is not configured\n"); - } - - msg(OMC_MSG_L1, "Cleaning up\n"); - delivery_free(&ctx); - globals_free(); - tpl_free(); - - msg(OMC_MSG_L1, "Done!\n"); - return 0; -} - diff --git a/src/recipe.c b/src/recipe.c index 43899b8..e51fde6 100644 --- a/src/recipe.c +++ b/src/recipe.c @@ -20,7 +20,7 @@ int recipe_clone(char *recipe_dir, char *url, char *gitref, char **result) { if (!access(destdir, F_OK)) { if (!strcmp(destdir, "/")) { - fprintf(stderr, "OMC is misconfigured. Please check your output path(s) immediately.\n"); + fprintf(stderr, "STASIS is misconfigured. Please check your output path(s) immediately.\n"); fprintf(stderr, "recipe_dir = '%s'\nreponame = '%s'\ndestdir = '%s'\n", recipe_dir, reponame, destdir); exit(1); diff --git a/src/relocation.c b/src/relocation.c index 586d1ba..a86530d 100644 --- a/src/relocation.c +++ b/src/relocation.c @@ -24,7 +24,7 @@ * @return 0 on success, -1 on error */ int replace_text(char *original, const char *target, const char *replacement, unsigned flags) { - char buffer[OMC_BUFSIZ]; + char buffer[STASIS_BUFSIZ]; char *pos = original; char *match = NULL; size_t original_len = strlen(original); @@ -104,7 +104,7 @@ int replace_text(char *original, const char *target, const char *replacement, un */ int file_replace_text(const char* filename, const char* target, const char* replacement, unsigned flags) { int result; - char buffer[OMC_BUFSIZ]; + char buffer[STASIS_BUFSIZ]; char tempfilename[] = "tempfileXXXXXX"; FILE *fp; FILE *tfp; diff --git a/src/stasis_indexer.c b/src/stasis_indexer.c new file mode 100644 index 0000000..fb231e0 --- /dev/null +++ b/src/stasis_indexer.c @@ -0,0 +1,753 @@ +#include +#include +#include "core.h" + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"destdir", required_argument, 0, 'd'}, + {"verbose", no_argument, 0, 'v'}, + {"unbuffered", no_argument, 0, 'U'}, + {"web", no_argument, 0, 'w'}, + {0, 0, 0, 0}, +}; + +const char *long_options_help[] = { + "Display this usage statement", + "Destination directory", + "Increase output verbosity", + "Disable line buffering", + "Generate HTML indexes (requires pandoc)", + NULL, +}; + +static void usage(char *name) { + int maxopts = sizeof(long_options) / sizeof(long_options[0]); + unsigned char *opts = calloc(maxopts + 1, sizeof(char)); + for (int i = 0; i < maxopts; i++) { + opts[i] = long_options[i].val; + } + printf("usage: %s [-%s] {{STASIS_ROOT}...}\n", name, opts); + guard_free(opts); + + for (int i = 0; i < maxopts - 1; i++) { + char line[255]; + sprintf(line, " --%s -%c %-20s", long_options[i].name, long_options[i].val, long_options_help[i]); + puts(line); + } + +} + +int indexer_combine_rootdirs(const char *dest, char **rootdirs, const size_t rootdirs_total) { + char cmd[PATH_MAX]; + + memset(cmd, 0, sizeof(cmd)); + sprintf(cmd, "rsync -ah%s --delete --exclude 'tools/' --exclude 'tmp/' --exclude 'build/' ", globals.verbose ? "v" : "q"); + for (size_t i = 0; i < rootdirs_total; i++) { + sprintf(cmd + strlen(cmd), "'%s'/ ", rootdirs[i]); + } + sprintf(cmd + strlen(cmd), "%s/", dest); + + if (globals.verbose) { + puts(cmd); + } + + if (system(cmd)) { + return -1; + } + return 0; +} + +int indexer_wheels(struct Delivery *ctx) { + return delivery_index_wheel_artifacts(ctx); +} + +int indexer_load_metadata(struct Delivery *ctx, const char *filename) { + char line[STASIS_NAME_MAX] = {0}; + FILE *fp; + + fp = fopen(filename, "r"); + if (!fp) { + return -1; + } + + while (fgets(line, sizeof(line) - 1, fp) != NULL) { + char **parts = split(line, " ", 1); + char *name = parts[0]; + char *value = parts[1]; + strip(value); + if (!strcmp(name, "name")) { + ctx->meta.name = strdup(value); + } else if (!strcmp(name, "version")) { + ctx->meta.version = strdup(value); + } else if (!strcmp(name, "rc")) { + ctx->meta.rc = (int) strtol(value, NULL, 10); + } else if (!strcmp(name, "python")) { + ctx->meta.python = strdup(value); + } else if (!strcmp(name, "python_compact")) { + ctx->meta.python_compact = strdup(value); + } else if (!strcmp(name, "mission")) { + ctx->meta.mission = strdup(value); + } else if (!strcmp(name, "codename")) { + ctx->meta.codename = strdup(value); + } else if (!strcmp(name, "platform")) { + ctx->system.platform = calloc(DELIVERY_PLATFORM_MAX, sizeof(*ctx->system.platform)); + char **platform = split(value, " ", 0); + ctx->system.platform[DELIVERY_PLATFORM] = platform[DELIVERY_PLATFORM]; + ctx->system.platform[DELIVERY_PLATFORM_CONDA_SUBDIR] = platform[DELIVERY_PLATFORM_CONDA_SUBDIR]; + ctx->system.platform[DELIVERY_PLATFORM_CONDA_INSTALLER] = platform[DELIVERY_PLATFORM_CONDA_INSTALLER]; + ctx->system.platform[DELIVERY_PLATFORM_RELEASE] = platform[DELIVERY_PLATFORM_RELEASE]; + } else if (!strcmp(name, "arch")) { + ctx->system.arch = strdup(value); + } else if (!strcmp(name, "time")) { + ctx->info.time_str_epoch = strdup(value); + } else if (!strcmp(name, "release_fmt")) { + ctx->rules.release_fmt = strdup(value); + } else if (!strcmp(name, "release_name")) { + ctx->info.release_name = strdup(value); + } else if (!strcmp(name, "build_name_fmt")) { + ctx->rules.build_name_fmt = strdup(value); + } else if (!strcmp(name, "build_name")) { + ctx->info.build_name = strdup(value); + } else if (!strcmp(name, "build_number_fmt")) { + ctx->rules.build_number_fmt = strdup(value); + } else if (!strcmp(name, "build_number")) { + ctx->info.build_number = strdup(value); + } else if (!strcmp(name, "conda_installer_baseurl")) { + ctx->conda.installer_baseurl = strdup(value); + } else if (!strcmp(name, "conda_installer_name")) { + ctx->conda.installer_name = strdup(value); + } else if (!strcmp(name, "conda_installer_version")) { + ctx->conda.installer_version = strdup(value); + } else if (!strcmp(name, "conda_installer_platform")) { + ctx->conda.installer_platform = strdup(value); + } else if (!strcmp(name, "conda_installer_arch")) { + ctx->conda.installer_arch = strdup(value); + } + GENERIC_ARRAY_FREE(parts); + } + fclose(fp); + + return 0; +} + +int indexer_get_files(struct StrList **out, const char *path, const char *pattern, ...) { + va_list args; + va_start(args, pattern); + char userpattern[PATH_MAX] = {0}; + vsprintf(userpattern, pattern, args); + va_end(args); + struct StrList *list = listdir(path); + if (!list) { + return -1; + } + + if (!(*out)) { + (*out) = strlist_init(); + if (!(*out)) { + guard_strlist_free(&list); + return -1; + } + } + + size_t no_match = 0; + for (size_t i = 0; i < strlist_count(list); i++) { + char *item = strlist_item(list, i); + if (fnmatch(userpattern, item, 0)) { + no_match++; + continue; + } else { + strlist_append(&(*out), item); + } + } + if (no_match >= strlist_count(list)) { + fprintf(stderr, "no files matching the pattern: %s\n", userpattern); + guard_strlist_free(&list); + return -1; + } + guard_strlist_free(&list); + return 0; +} + +int get_latest_rc(struct Delivery ctx[], size_t nelem) { + int result = 0; + for (size_t i = 0; i < nelem; i++) { + if (ctx[i].meta.rc > result) { + result = ctx[i].meta.rc; + } + } + return result; +} + +struct Delivery **get_latest_deliveries(struct Delivery ctx[], size_t nelem) { + struct Delivery **result = NULL; + int latest = 0; + size_t n = 0; + + result = calloc(nelem + 1, sizeof(result)); + if (!result) { + fprintf(stderr, "Unable to allocate %zu bytes for result delivery array: %s\n", nelem * sizeof(result), strerror(errno)); + return NULL; + } + + latest = get_latest_rc(ctx, nelem); + for (size_t i = 0; i < nelem; i++) { + if (ctx[i].meta.rc == latest) { + result[n] = &ctx[i]; + n++; + } + } + + return result; +} + +int micromamba(const char *write_to, const char *prefix, char *command, ...) { + struct utsname sys; + uname(&sys); + + tolower_s(sys.sysname); + if (!strcmp(sys.sysname, "darwin")) { + strcpy(sys.sysname, "osx"); + } + + if (!strcmp(sys.machine, "x86_64")) { + strcpy(sys.machine, "64"); + } + + char url[PATH_MAX]; + sprintf(url, "https://micro.mamba.pm/api/micromamba/%s-%s/latest", sys.sysname, sys.machine); + if (access("latest", F_OK)) { + download(url, "latest", NULL); + } + + char mmbin[PATH_MAX]; + sprintf(mmbin, "%s/micromamba", write_to); + + if (access(mmbin, F_OK)) { + char untarcmd[PATH_MAX]; + mkdirs(write_to, 0755); + sprintf(untarcmd, "tar -xvf latest -C %s --strip-components=1 bin/micromamba 1>/dev/null", write_to); + system(untarcmd); + } + + char cmd[STASIS_BUFSIZ]; + memset(cmd, 0, sizeof(cmd)); + sprintf(cmd, "%s -r %s -p %s ", mmbin, prefix, prefix); + va_list args; + va_start(args, command); + vsprintf(cmd + strlen(cmd), command, args); + va_end(args); + + mkdirs(prefix, 0755); + + char rcpath[PATH_MAX]; + sprintf(rcpath, "%s/.condarc", prefix); + touch(rcpath); + + setenv("CONDARC", rcpath, 1); + setenv("MAMBA_ROOT_PREFIX", prefix, 1); + int status = system(cmd); + unsetenv("MAMBA_ROOT_PREFIX"); + + return status; +} + +int indexer_make_website(struct Delivery *ctx) { + char cmd[PATH_MAX]; + char *inputs[] = { + ctx->storage.delivery_dir, "/README.md", + ctx->storage.results_dir, "/README.md", + }; + + if (!find_program("pandoc")) { + return 0; + } + + for (size_t i = 0; i < sizeof(inputs) / sizeof(*inputs); i += 2) { + char fullpath[PATH_MAX]; + memset(fullpath, 0, sizeof(fullpath)); + sprintf(fullpath, "%s/%s", inputs[i], inputs[i + 1]); + if (access(fullpath, F_OK)) { + continue; + } + // Converts a markdown file to html + strcpy(cmd, "pandoc "); + strcat(cmd, fullpath); + strcat(cmd, " "); + strcat(cmd, "-o "); + strcat(cmd, inputs[i]); + strcat(cmd, "/index.html"); + if (globals.verbose) { + puts(cmd); + } + // This might be negative when killed by a signal. + // Otherwise, the return code is not critical to us. + if (system(cmd) < 0) { + return 1; + } + } + + return 0; +} + +int indexer_conda(struct Delivery *ctx) { + int status = 0; + char prefix[PATH_MAX]; + sprintf(prefix, "%s/%s", ctx->storage.tmpdir, "indexer"); + + status += micromamba(ctx->storage.tmpdir, prefix, "config prepend --env channels conda-forge"); + if (!globals.verbose) { + status += micromamba(ctx->storage.tmpdir, prefix, "config set --env quiet true"); + } + status += micromamba(ctx->storage.tmpdir, prefix, "config set --env always_yes true"); + status += micromamba(ctx->storage.tmpdir, prefix, "install conda-build"); + status += micromamba(ctx->storage.tmpdir, prefix, "run conda index %s", ctx->storage.conda_artifact_dir); + return status; +} + +static struct StrList *get_architectures(struct Delivery ctx[], size_t nelem) { + struct StrList *architectures = strlist_init(); + for (size_t i = 0; i < nelem; i++) { + if (!strstr_array(architectures->data, ctx[i].system.arch)) { + strlist_append(&architectures, ctx[i].system.arch); + } + } + return architectures; +} + +static struct StrList *get_platforms(struct Delivery ctx[], size_t nelem) { + struct StrList *platforms = strlist_init(); + for (size_t i = 0; i < nelem; i++) { + if (!strstr_array(platforms->data, ctx[i].system.platform[DELIVERY_PLATFORM_RELEASE])) { + strlist_append(&platforms, ctx[i].system.platform[DELIVERY_PLATFORM_RELEASE]); + } + } + return platforms; +} + +int indexer_symlinks(struct Delivery ctx[], size_t nelem) { + struct Delivery **data = NULL; + data = get_latest_deliveries(ctx, nelem); + //int latest = get_latest_rc(ctx, nelem); + + if (!pushd(ctx->storage.delivery_dir)) { + for (size_t i = 0; i < nelem; i++) { + char link_name_spec[PATH_MAX]; + char link_name_readme[PATH_MAX]; + + char file_name_spec[PATH_MAX]; + char file_name_readme[PATH_MAX]; + + if (!data[i]) { + continue; + } + sprintf(link_name_spec, "latest-py%s-%s-%s.yml", data[i]->meta.python_compact, data[i]->system.platform[DELIVERY_PLATFORM_RELEASE], data[i]->system.arch); + sprintf(file_name_spec, "%s.yml", data[i]->info.release_name); + + sprintf(link_name_readme, "README-py%s-%s-%s.md", data[i]->meta.python_compact, data[i]->system.platform[DELIVERY_PLATFORM_RELEASE], data[i]->system.arch); + sprintf(file_name_readme, "README-%s.md", data[i]->info.release_name); + + if (!access(link_name_spec, F_OK)) { + if (unlink(link_name_spec)) { + fprintf(stderr, "Unable to remove spec link: %s\n", link_name_spec); + } + } + if (!access(link_name_readme, F_OK)) { + if (unlink(link_name_readme)) { + fprintf(stderr, "Unable to remove readme link: %s\n", link_name_readme); + } + } + + if (globals.verbose) { + printf("%s -> %s\n", file_name_spec, link_name_spec); + } + if (symlink(file_name_spec, link_name_spec)) { + fprintf(stderr, "Unable to link %s as %s\n", file_name_spec, link_name_spec); + } + + if (globals.verbose) { + printf("%s -> %s\n", file_name_readme, link_name_readme); + } + if (symlink(file_name_readme, link_name_readme)) { + fprintf(stderr, "Unable to link %s as %s\n", file_name_readme, link_name_readme); + } + } + popd(); + } else { + fprintf(stderr, "Unable to enter delivery directory: %s\n", ctx->storage.delivery_dir); + guard_free(data); + return -1; + } + + // "latest" is an array of pointers to ctx[]. Do not free the contents of the array. + guard_free(data); + return 0; +} + +int indexer_readmes(struct Delivery ctx[], size_t nelem) { + struct Delivery **latest = NULL; + latest = get_latest_deliveries(ctx, nelem); + + char indexfile[PATH_MAX] = {0}; + sprintf(indexfile, "%s/README.md", ctx->storage.delivery_dir); + + if (!pushd(ctx->storage.delivery_dir)) { + FILE *indexfp; + indexfp = fopen(indexfile, "w+"); + if (!indexfp) { + fprintf(stderr, "Unable to open %s for writing\n", indexfile); + return -1; + } + struct StrList *archs = get_architectures(*latest, nelem); + struct StrList *platforms = get_platforms(*latest, nelem); + + fprintf(indexfp, "# %s-%s\n\n", ctx->meta.name, ctx->meta.version); + fprintf(indexfp, "## Current Release\n\n"); + for (size_t p = 0; p < strlist_count(platforms); p++) { + char *platform = strlist_item(platforms, p); + for (size_t a = 0; a < strlist_count(archs); a++) { + char *arch = strlist_item(archs, a); + int have_combo = 0; + for (size_t i = 0; i < nelem; i++) { + if (strstr(latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], platform) && strstr(latest[i]->system.arch, arch)) { + have_combo = 1; + } + } + if (!have_combo) { + continue; + } + fprintf(indexfp, "### %s-%s\n\n", platform, arch); + + fprintf(indexfp, "|Release|Info|Receipt|\n"); + fprintf(indexfp, "|:----:|:----:|:----:|\n"); + for (size_t i = 0; i < nelem; i++) { + char link_name[PATH_MAX]; + char readme_name[PATH_MAX]; + char conf_name[PATH_MAX]; + char conf_name_relative[PATH_MAX]; + if (!latest[i]) { + continue; + } + sprintf(link_name, "latest-py%s-%s-%s.yml", latest[i]->meta.python_compact, latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], latest[i]->system.arch); + sprintf(readme_name, "README-py%s-%s-%s.md", latest[i]->meta.python_compact, latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], latest[i]->system.arch); + sprintf(conf_name, "%s.ini", latest[i]->info.release_name); + sprintf(conf_name_relative, "../config/%s-rendered.ini", latest[i]->info.release_name); + if (strstr(link_name, platform) && strstr(link_name, arch)) { + fprintf(indexfp, "|[%s](%s)|[%s](%s)|[%s](%s)|\n", link_name, link_name, readme_name, readme_name, conf_name, conf_name_relative); + } + } + fprintf(indexfp, "\n"); + } + fprintf(indexfp, "\n"); + } + guard_strlist_free(&archs); + guard_strlist_free(&platforms); + fclose(indexfp); + popd(); + } else { + fprintf(stderr, "Unable to enter delivery directory: %s\n", ctx->storage.delivery_dir); + guard_free(latest); + return -1; + } + + // "latest" is an array of pointers to ctxs[]. Do not free the contents of the array. + guard_free(latest); + return 0; +} + +int indexer_junitxml_report(struct Delivery ctx[], size_t nelem) { + struct Delivery **latest = NULL; + latest = get_latest_deliveries(ctx, nelem); + + char indexfile[PATH_MAX] = {0}; + sprintf(indexfile, "%s/README.md", ctx->storage.results_dir); + + struct StrList *file_listing = listdir(ctx->storage.results_dir); + if (!file_listing) { + // no test results to process + return 0; + } + + if (!pushd(ctx->storage.results_dir)) { + FILE *indexfp; + indexfp = fopen(indexfile, "w+"); + if (!indexfp) { + fprintf(stderr, "Unable to open %s for writing\n", indexfile); + return -1; + } + struct StrList *archs = get_architectures(*latest, nelem); + struct StrList *platforms = get_platforms(*latest, nelem); + + fprintf(indexfp, "# %s-%s Test Report\n\n", ctx->meta.name, ctx->meta.version); + fprintf(indexfp, "## Current Release\n\n"); + for (size_t p = 0; p < strlist_count(platforms); p++) { + char *platform = strlist_item(platforms, p); + for (size_t a = 0; a < strlist_count(archs); a++) { + char *arch = strlist_item(archs, a); + int have_combo = 0; + for (size_t i = 0; i < nelem; i++) { + if (strstr(latest[i]->system.platform[DELIVERY_PLATFORM_RELEASE], platform) && strstr(latest[i]->system.arch, arch)) { + have_combo = 1; + break; + } + } + if (!have_combo) { + continue; + } + fprintf(indexfp, "### %s-%s\n\n", platform, arch); + + fprintf(indexfp, "|Suite|Duration|Fail |Skip |Error |\n"); + fprintf(indexfp, "|:----|:------:|:------:|:---:|:----:|\n"); + for (size_t f = 0; f < strlist_count(file_listing); f++) { + char *filename = strlist_item(file_listing, f); + if (!endswith(filename, ".xml")) { + continue; + } + + if (strstr(filename, platform) && strstr(filename, arch)) { + struct JUNIT_Testsuite *testsuite = junitxml_testsuite_read(filename); + if (testsuite) { + if (globals.verbose) { + printf("%s: duration: %0.4f, failed: %d, skipped: %d, errors: %d\n", filename, testsuite->time, testsuite->failures, testsuite->skipped, testsuite->errors); + } + fprintf(indexfp, "|[%s](%s)|%0.4f|%d|%d|%d|\n", filename, filename, testsuite->time, testsuite->failures, testsuite->skipped, testsuite->errors); + /* + * TODO: Display failure/skip/error output. + * + for (size_t i = 0; i < testsuite->_tc_inuse; i++) { + if (testsuite->testcase[i]->tc_result_state_type) { + printf("testcase: %s :: %s\n", testsuite->testcase[i]->classname, testsuite->testcase[i]->name); + if (testsuite->testcase[i]->tc_result_state_type == JUNIT_RESULT_STATE_FAILURE) { + printf("failure: %s\n", testsuite->testcase[i]->result_state.failure->message); + } else if (testsuite->testcase[i]->tc_result_state_type == JUNIT_RESULT_STATE_SKIPPED) { + printf("skipped: %s\n", testsuite->testcase[i]->result_state.skipped->message); + } + } + } + */ + junitxml_testsuite_free(&testsuite); + } else { + fprintf(stderr, "bad test suite: %s: %s\n", strerror(errno), filename); + continue; + } + } + } + fprintf(indexfp, "\n"); + } + fprintf(indexfp, "\n"); + } + guard_strlist_free(&archs); + guard_strlist_free(&platforms); + fclose(indexfp); + popd(); + } else { + fprintf(stderr, "Unable to enter delivery directory: %s\n", ctx->storage.delivery_dir); + guard_free(latest); + return -1; + } + + // "latest" is an array of pointers to ctxs[]. Do not free the contents of the array. + guard_free(latest); + return 0; +} + +void indexer_init_dirs(struct Delivery *ctx, const char *workdir) { + path_store(&ctx->storage.root, PATH_MAX, workdir, ""); + path_store(&ctx->storage.tmpdir, PATH_MAX, ctx->storage.root, "tmp"); + if (delivery_init_tmpdir(ctx)) { + fprintf(stderr, "Failed to configure temporary storage directory\n"); + exit(1); + } + path_store(&ctx->storage.output_dir, PATH_MAX, ctx->storage.root, "output"); + path_store(&ctx->storage.cfgdump_dir, PATH_MAX, ctx->storage.output_dir, "config"); + path_store(&ctx->storage.meta_dir, PATH_MAX, ctx->storage.output_dir, "meta"); + path_store(&ctx->storage.delivery_dir, PATH_MAX, ctx->storage.output_dir, "delivery"); + path_store(&ctx->storage.package_dir, PATH_MAX, ctx->storage.output_dir, "packages"); + path_store(&ctx->storage.results_dir, PATH_MAX, ctx->storage.output_dir, "results"); + path_store(&ctx->storage.wheel_artifact_dir, PATH_MAX, ctx->storage.package_dir, "wheels"); + path_store(&ctx->storage.conda_artifact_dir, PATH_MAX, ctx->storage.package_dir, "conda"); +} + +int main(int argc, char *argv[]) { + size_t rootdirs_total = 0; + char *destdir = NULL; + char **rootdirs = NULL; + int do_html = 0; + int c = 0; + int option_index = 0; + while ((c = getopt_long(argc, argv, "hd:vUw", long_options, &option_index)) != -1) { + switch (c) { + case 'h': + usage(path_basename(argv[0])); + exit(0); + case 'd': + destdir = strdup(optarg); + break; + case 'U': + fflush(stdout); + fflush(stderr); + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + break; + case 'v': + globals.verbose = 1; + break; + case 'w': + do_html = 1; + break; + case '?': + default: + exit(1); + } + } + + int current_index = optind; + if (optind < argc) { + rootdirs_total = argc - current_index; + while (optind < argc) { + // use first positional argument + rootdirs = &argv[optind++]; + break; + } + } + + if (isempty(destdir)) { + destdir = strdup("output"); + } + + if (!rootdirs || !rootdirs_total) { + fprintf(stderr, "You must specify at least one STASIS root directory to index\n"); + exit(1); + } else { + for (size_t i = 0; i < rootdirs_total; i++) { + if (isempty(rootdirs[i]) || !strcmp(rootdirs[i], "/") || !strcmp(rootdirs[i], "\\")) { + SYSERROR("Unsafe directory: %s", rootdirs[i]); + exit(1); + } + } + } + + char *workdir; + char workdir_template[PATH_MAX]; + char *system_tmp = getenv("TMPDIR"); + if (system_tmp) { + strcat(workdir_template, system_tmp); + } else { + strcat(workdir_template, "/tmp"); + } + strcat(workdir_template, "/stasis-combine.XXXXXX"); + workdir = mkdtemp(workdir_template); + if (!workdir) { + SYSERROR("Unable to create temporary directory: %s", workdir_template); + exit(1); + } else if (isempty(workdir) || !strcmp(workdir, "/") || !strcmp(workdir, "\\")) { + SYSERROR("Unsafe directory: %s", workdir); + exit(1); + } + + struct Delivery ctx; + memset(&ctx, 0, sizeof(ctx)); + + printf(BANNER, VERSION, AUTHOR); + + indexer_init_dirs(&ctx, workdir); + + msg(STASIS_MSG_L1, "%s delivery root %s\n", + rootdirs_total > 1 ? "Merging" : "Indexing", + rootdirs_total > 1 ? "directories" : "directory"); + if (indexer_combine_rootdirs(workdir, rootdirs, rootdirs_total)) { + SYSERROR("%s", "Copy operation failed"); + rmtree(workdir); + exit(1); + } + + if (access(ctx.storage.conda_artifact_dir, F_OK)) { + mkdirs(ctx.storage.conda_artifact_dir, 0755); + } + + if (access(ctx.storage.wheel_artifact_dir, F_OK)) { + mkdirs(ctx.storage.wheel_artifact_dir, 0755); + } + + msg(STASIS_MSG_L1, "Indexing conda packages\n"); + if (indexer_conda(&ctx)) { + SYSERROR("%s", "Conda package indexing operation failed"); + exit(1); + } + + msg(STASIS_MSG_L1, "Indexing wheel packages\n"); + if (indexer_wheels(&ctx)) { + SYSERROR("%s", "Python package indexing operation failed"); + exit(1); + } + + msg(STASIS_MSG_L1, "Loading metadata\n"); + struct StrList *metafiles = NULL; + indexer_get_files(&metafiles, ctx.storage.meta_dir, "*.stasis"); + strlist_sort(metafiles, STASIS_SORT_LEN_ASCENDING); + struct Delivery local[strlist_count(metafiles)]; + + for (size_t i = 0; i < strlist_count(metafiles); i++) { + char *item = strlist_item(metafiles, i); + memset(&local[i], 0, sizeof(ctx)); + memcpy(&local[i], &ctx, sizeof(ctx)); + char path[PATH_MAX]; + sprintf(path, "%s/%s", ctx.storage.meta_dir, item); + if (globals.verbose) { + puts(path); + } + indexer_load_metadata(&local[i], path); + } + + msg(STASIS_MSG_L1, "Generating links to latest release iteration\n"); + if (indexer_symlinks(local, strlist_count(metafiles))) { + SYSERROR("%s", "Link generation failed"); + exit(1); + } + + msg(STASIS_MSG_L1, "Generating README.md\n"); + if (indexer_readmes(local, strlist_count(metafiles))) { + SYSERROR("%s", "README indexing operation failed"); + exit(1); + } + + msg(STASIS_MSG_L1, "Indexing test results\n"); + if (indexer_junitxml_report(local, strlist_count(metafiles))) { + SYSERROR("%s", "Test result indexing operation failed"); + exit(1); + } + + if (do_html) { + msg(STASIS_MSG_L1, "Generating HTML indexes\n"); + if (indexer_make_website(local)) { + SYSERROR("%s", "Site creation failed"); + exit(1); + } + } + + msg(STASIS_MSG_L1, "Copying indexed delivery to '%s'\n", destdir); + char cmd[PATH_MAX]; + memset(cmd, 0, sizeof(cmd)); + sprintf(cmd, "rsync -ah%s --delete --exclude 'tmp/' --exclude 'tools/' '%s/' '%s/'", globals.verbose ? "v" : "q", workdir, destdir); + guard_free(destdir); + + if (globals.verbose) { + puts(cmd); + } + + if (system(cmd)) { + SYSERROR("%s", "Copy operation failed"); + rmtree(workdir); + exit(1); + } + + msg(STASIS_MSG_L1, "Removing work directory: %s\n", workdir); + if (rmtree(workdir)) { + SYSERROR("Failed to remove work directory: %s", strerror(errno)); + } + + guard_strlist_free(&metafiles); + delivery_free(&ctx); + globals_free(); + msg(STASIS_MSG_L1, "Done!\n"); + return 0; +} diff --git a/src/stasis_main.c b/src/stasis_main.c new file mode 100644 index 0000000..8b13b98 --- /dev/null +++ b/src/stasis_main.c @@ -0,0 +1,612 @@ +#include +#include +#include +#include +#include +#include "core.h" + +#define OPT_ALWAYS_UPDATE_BASE 1000 +#define OPT_NO_DOCKER 1001 +#define OPT_NO_ARTIFACTORY 1002 +#define OPT_NO_TESTING 1003 +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {"continue-on-error", no_argument, 0, 'C'}, + {"config", required_argument, 0, 'c'}, + {"python", required_argument, 0, 'p'}, + {"verbose", no_argument, 0, 'v'}, + {"unbuffered", no_argument, 0, 'U'}, + {"update-base", no_argument, 0, OPT_ALWAYS_UPDATE_BASE}, + {"no-docker", no_argument, 0, OPT_NO_DOCKER}, + {"no-artifactory", no_argument, 0, OPT_NO_ARTIFACTORY}, + {"no-testing", no_argument, 0, OPT_NO_TESTING}, + {0, 0, 0, 0}, +}; + +const char *long_options_help[] = { + "Display this usage statement", + "Display program version", + "Allow tests to fail", + "Read configuration file", + "Override version of Python in configuration", + "Increase output verbosity", + "Disable line buffering", + "Update conda installation prior to STASIS environment creation", + "Do not build docker images", + "Do not upload artifacts to Artifactory", + "Do not execute test scripts", + NULL, +}; + +static int get_option_max_width(struct option option[]) { + int i = 0; + int max = 0; + const int indent = 4; + while (option[i].name != 0) { + int len = (int) strlen(option[i].name); + if (option[i].has_arg) { + len += indent; + } + if (len > max) { + max = len; + } + i++; + } + return max; +} + +static void usage(char *progname) { + printf("usage: %s ", progname); + printf("[-"); + for (int x = 0; long_options[x].val != 0; x++) { + if (long_options[x].has_arg == no_argument && long_options[x].val <= 'z') { + putchar(long_options[x].val); + } + } + printf("] {DELIVERY_FILE}\n"); + + int width = get_option_max_width(long_options); + for (int x = 0; long_options[x].name != 0; x++) { + char tmp[STASIS_NAME_MAX] = {0}; + char output[sizeof(tmp)] = {0}; + char opt_long[50] = {0}; // --? [ARG]? + char opt_short[50] = {0}; // -? [ARG]? + + strcat(opt_long, "--"); + strcat(opt_long, long_options[x].name); + if (long_options[x].has_arg) { + strcat(opt_long, " ARG"); + } + + if (long_options[x].val <= 'z') { + strcat(opt_short, "-"); + opt_short[1] = (char) long_options[x].val; + if (long_options[x].has_arg) { + strcat(opt_short, " ARG"); + } + } else { + strcat(opt_short, " "); + } + + sprintf(tmp, " %%-%ds\t%%s\t\t%%s", width + 4); + sprintf(output, tmp, opt_long, opt_short, long_options_help[x]); + puts(output); + } +} + + + +static void check_system_requirements(struct Delivery *ctx) { + const char *tools_required[] = { + "rsync", + NULL, + }; + + msg(STASIS_MSG_L1, "Checking system requirements\n"); + for (size_t i = 0; tools_required[i] != NULL; i++) { + if (!find_program(tools_required[i])) { + msg(STASIS_MSG_L2 | STASIS_MSG_ERROR, "'%s' must be installed.\n", tools_required[i]); + exit(1); + } + } + + if (!globals.tmpdir && !ctx->storage.tmpdir) { + delivery_init_tmpdir(ctx); + } + + struct DockerCapabilities dcap; + if (!docker_capable(&dcap)) { + msg(STASIS_MSG_L2 | STASIS_MSG_WARN, "Docker is broken\n"); + msg(STASIS_MSG_L3, "Available: %s\n", dcap.available ? "Yes" : "No"); + msg(STASIS_MSG_L3, "Usable: %s\n", dcap.usable ? "Yes" : "No"); + msg(STASIS_MSG_L3, "Podman [Docker Emulation]: %s\n", dcap.podman ? "Yes" : "No"); + msg(STASIS_MSG_L3, "Build plugin(s): "); + if (dcap.usable) { + if (dcap.build & STASIS_DOCKER_BUILD) { + printf("build "); + } + if (dcap.build & STASIS_DOCKER_BUILD_X) { + printf("buildx "); + } + puts(""); + } else { + printf("N/A\n"); + } + + // disable docker builds + globals.enable_docker = false; + } +} + +int main(int argc, char *argv[]) { + struct Delivery ctx; + struct Process proc = { + .f_stdout = "", + .f_stderr = "", + .redirect_stderr = 0, + }; + char env_name[STASIS_NAME_MAX] = {0}; + char env_name_testing[STASIS_NAME_MAX] = {0}; + char *delivery_input = NULL; + char *config_input = NULL; + char installer_url[PATH_MAX]; + char python_override_version[STASIS_NAME_MAX]; + int user_disabled_docker = false; + + memset(env_name, 0, sizeof(env_name)); + memset(env_name_testing, 0, sizeof(env_name_testing)); + memset(installer_url, 0, sizeof(installer_url)); + memset(python_override_version, 0, sizeof(python_override_version)); + memset(&proc, 0, sizeof(proc)); + memset(&ctx, 0, sizeof(ctx)); + + int c; + int option_index = 0; + while ((c = getopt_long(argc, argv, "hVCc:p:vU", long_options, &option_index)) != -1) { + switch (c) { + case 'h': + usage(path_basename(argv[0])); + exit(0); + case 'V': + puts(VERSION); + exit(0); + case 'c': + config_input = strdup(optarg); + break; + case 'C': + globals.continue_on_error = true; + break; + case 'p': + strcpy(python_override_version, optarg); + break; + case OPT_ALWAYS_UPDATE_BASE: + globals.always_update_base_environment = true; + break; + case 'U': + setenv("PYTHONUNBUFFERED", "1", 1); + fflush(stdout); + fflush(stderr); + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + break; + case 'v': + globals.verbose = true; + break; + case OPT_NO_DOCKER: + globals.enable_docker = false; + user_disabled_docker = true; + break; + case OPT_NO_ARTIFACTORY: + globals.enable_artifactory = false; + break; + case OPT_NO_TESTING: + globals.enable_testing = false; + break; + case '?': + default: + exit(1); + } + } + + if (optind < argc) { + while (optind < argc) { + // use first positional argument + delivery_input = argv[optind++]; + break; + } + } + + if (!delivery_input) { + fprintf(stderr, "error: a DELIVERY_FILE is required\n"); + usage(path_basename(argv[0])); + exit(1); + } + + printf(BANNER, VERSION, AUTHOR); + + msg(STASIS_MSG_L1, "Setup\n"); + + // Expose variables for use with the template engine + // NOTE: These pointers are populated by delivery_init() so please avoid using + // tpl_render() until then. + tpl_register("meta.name", &ctx.meta.name); + tpl_register("meta.version", &ctx.meta.version); + tpl_register("meta.codename", &ctx.meta.codename); + tpl_register("meta.mission", &ctx.meta.mission); + tpl_register("meta.python", &ctx.meta.python); + tpl_register("meta.python_compact", &ctx.meta.python_compact); + tpl_register("info.time_str_epoch", &ctx.info.time_str_epoch); + tpl_register("info.release_name", &ctx.info.release_name); + tpl_register("info.build_name", &ctx.info.build_name); + tpl_register("info.build_number", &ctx.info.build_number); + tpl_register("storage.tmpdir", &ctx.storage.tmpdir); + tpl_register("storage.output_dir", &ctx.storage.output_dir); + tpl_register("storage.delivery_dir", &ctx.storage.delivery_dir); + tpl_register("storage.conda_artifact_dir", &ctx.storage.conda_artifact_dir); + tpl_register("storage.wheel_artifact_dir", &ctx.storage.wheel_artifact_dir); + tpl_register("storage.build_sources_dir", &ctx.storage.build_sources_dir); + tpl_register("storage.build_docker_dir", &ctx.storage.build_docker_dir); + tpl_register("storage.results_dir", &ctx.storage.results_dir); + tpl_register("conda.installer_baseurl", &ctx.conda.installer_baseurl); + tpl_register("conda.installer_name", &ctx.conda.installer_name); + tpl_register("conda.installer_version", &ctx.conda.installer_version); + tpl_register("conda.installer_arch", &ctx.conda.installer_arch); + tpl_register("conda.installer_platform", &ctx.conda.installer_platform); + tpl_register("deploy.jfrog.repo", &globals.jfrog.repo); + tpl_register("deploy.jfrog.url", &globals.jfrog.url); + tpl_register("deploy.docker.registry", &ctx.deploy.docker.registry); + tpl_register("workaround.tox_posargs", &globals.workaround.tox_posargs); + + // Set up PREFIX/etc directory information + // The user may manipulate the base directory path with STASIS_SYSCONFDIR + // environment variable + char stasis_sysconfdir_tmp[PATH_MAX]; + if (getenv("STASIS_SYSCONFDIR")) { + strncpy(stasis_sysconfdir_tmp, getenv("STASIS_SYSCONFDIR"), sizeof(stasis_sysconfdir_tmp) - 1); + } else { + strncpy(stasis_sysconfdir_tmp, STASIS_SYSCONFDIR, sizeof(stasis_sysconfdir_tmp) - 1); + } + + globals.sysconfdir = realpath(stasis_sysconfdir_tmp, NULL); + if (!globals.sysconfdir) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L1, "Unable to resolve path to configuration directory: %s\n", stasis_sysconfdir_tmp); + exit(1); + } + + // Override Python version from command-line, if any + if (strlen(python_override_version)) { + guard_free(ctx.meta.python); + ctx.meta.python = strdup(python_override_version); + guard_free(ctx.meta.python_compact); + ctx.meta.python_compact = to_short_version(ctx.meta.python); + } + + if (!config_input) { + // no configuration passed by argument. use basic config. + char cfgfile[PATH_MAX * 2]; + sprintf(cfgfile, "%s/%s", globals.sysconfdir, "stasis.ini"); + if (!access(cfgfile, F_OK | R_OK)) { + config_input = strdup(cfgfile); + } else { + msg(STASIS_MSG_WARN, "STASIS global configuration is not readable, or does not exist: %s", cfgfile); + } + } + + if (config_input) { + msg(STASIS_MSG_L2, "Reading STASIS global configuration: %s\n", config_input); + ctx._stasis_ini_fp.cfg = ini_open(config_input); + if (!ctx._stasis_ini_fp.cfg) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Failed to read config file: %s, %s\n", delivery_input, strerror(errno)); + exit(1); + } + ctx._stasis_ini_fp.cfg_path = strdup(config_input); + guard_free(config_input); + } + + msg(STASIS_MSG_L2, "Reading STASIS delivery configuration: %s\n", delivery_input); + ctx._stasis_ini_fp.delivery = ini_open(delivery_input); + if (!ctx._stasis_ini_fp.delivery) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Failed to read delivery file: %s, %s\n", delivery_input, strerror(errno)); + exit(1); + } + ctx._stasis_ini_fp.delivery_path = strdup(delivery_input); + + msg(STASIS_MSG_L2, "Bootstrapping delivery context\n"); + if (bootstrap_build_info(&ctx)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Failed to bootstrap delivery context\n"); + exit(1); + } + + msg(STASIS_MSG_L2, "Initializing delivery context\n"); + if (delivery_init(&ctx)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Failed to initialize delivery context\n"); + exit(1); + } + check_system_requirements(&ctx); + + msg(STASIS_MSG_L2, "Configuring JFrog CLI\n"); + if (delivery_init_artifactory(&ctx)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "JFrog CLI configuration failed\n"); + exit(1); + } + + runtime_apply(ctx.runtime.environ); + strcpy(env_name, ctx.info.release_name); + strcpy(env_name_testing, env_name); + strcat(env_name_testing, "-test"); + + msg(STASIS_MSG_L1, "Overview\n"); + delivery_meta_show(&ctx); + delivery_conda_show(&ctx); + delivery_tests_show(&ctx); + if (globals.verbose) { + //delivery_runtime_show(&ctx); + } + + msg(STASIS_MSG_L1, "Conda setup\n"); + delivery_get_installer_url(&ctx, installer_url); + msg(STASIS_MSG_L2, "Downloading: %s\n", installer_url); + if (delivery_get_installer(&ctx, installer_url)) { + msg(STASIS_MSG_ERROR, "download failed: %s\n", installer_url); + exit(1); + } + + // Unlikely to occur: this should help prevent rmtree() from destroying your entire filesystem + // if path is "/" then, die + // or if empty string, die + if (!strcmp(ctx.storage.conda_install_prefix, DIR_SEP) || !strlen(ctx.storage.conda_install_prefix)) { + fprintf(stderr, "error: ctx.storage.conda_install_prefix is malformed!\n"); + exit(1); + } + + msg(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); + + char *pathvar = NULL; + pathvar = getenv("PATH"); + if (!pathvar) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "PATH variable is not set. Cannot continue.\n"); + exit(1); + } else { + char pathvar_tmp[STASIS_BUFSIZ]; + sprintf(pathvar_tmp, "%s/bin:%s", ctx.storage.conda_install_prefix, pathvar); + setenv("PATH", pathvar_tmp, 1); + pathvar = NULL; + } + + msg(STASIS_MSG_L1, "Creating release environment(s)\n"); + if (ctx.meta.based_on && strlen(ctx.meta.based_on)) { + if (conda_env_remove(env_name)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to remove release environment: %s\n", env_name_testing); + exit(1); + } + msg(STASIS_MSG_L2, "Based on release: %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 (conda_env_remove(env_name_testing)) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to remove testing environment\n"); + 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 release 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); + } + + 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 (pip_exec("install build")) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "'build' tool installation failed"); + exit(1); + } + + // Execute configuration-defined tests + if (globals.enable_testing) { + msg(STASIS_MSG_L1, "Begin test execution\n"); + delivery_tests_run(&ctx); + msg(STASIS_MSG_L1, "Rewriting test results\n"); + delivery_fixup_test_results(&ctx); + } else { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Test execution is disabled\n"); + } + + msg(STASIS_MSG_L1, "Generating deferred package listing\n"); + // Test succeeded so move on to producing package artifacts + delivery_defer_packages(&ctx, DEFER_CONDA); + delivery_defer_packages(&ctx, DEFER_PIP); + + if (ctx.conda.conda_packages_defer && strlist_count(ctx.conda.conda_packages_defer)) { + msg(STASIS_MSG_L2, "Building Conda recipe(s)\n"); + if (delivery_build_recipes(&ctx)) { + exit(1); + } + msg(STASIS_MSG_L3, "Copying artifacts\n"); + if (delivery_copy_conda_artifacts(&ctx)) { + exit(1); + } + msg(STASIS_MSG_L3, "Indexing artifacts\n"); + if (delivery_index_conda_artifacts(&ctx)) { + exit(1); + } + } + + if (strlist_count(ctx.conda.pip_packages_defer)) { + if (!(ctx.conda.wheels_packages = delivery_build_wheels(&ctx))) { + exit(1); + } + if (delivery_index_wheel_artifacts(&ctx)) { + exit(1); + } + + } + + // 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); + } + } 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); + } + } + + 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"); + } + + 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); + 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); + } + + 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); + } + + // Rewrite release environment output (i.e. set package origin(s) to point to the deployment server, etc.) + char specfile[PATH_MAX]; + sprintf(specfile, "%s/%s.yml", ctx.storage.delivery_dir, env_name); + msg(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); + + int want_docker = ini_section_search(&ctx._stasis_ini_fp.delivery, INI_SEARCH_BEGINS, "deploy:docker") ? true : false; + int want_artifactory = ini_section_search(&ctx._stasis_ini_fp.delivery, INI_SEARCH_BEGINS, "deploy:artifactory") ? true : false; + + if (want_docker) { + if (user_disabled_docker) { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Docker image building is disabled by CLI argument\n"); + } else { + char dockerfile[PATH_MAX] = {0}; + sprintf(dockerfile, "%s/%s", ctx.storage.build_docker_dir, "Dockerfile"); + if (globals.enable_docker) { + if (!access(dockerfile, F_OK)) { + msg(STASIS_MSG_L1, "Building Docker image\n"); + if (delivery_docker(&ctx)) { + msg(STASIS_MSG_L1 | STASIS_MSG_ERROR, "Failed to build docker image!\n"); + COE_CHECK_ABORT(1, "Failed to build docker image"); + } + } else { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Docker image building is disabled. No Dockerfile found in %s\n", ctx.storage.build_docker_dir); + } + } else { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Docker image building is disabled. System configuration error\n"); + } + } + } else { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Docker image building is disabled. deploy:docker is not configured\n"); + } + + 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); + + msg(STASIS_MSG_L1, "Dumping metadata\n"); + if (delivery_dump_metadata(&ctx)) { + msg(STASIS_MSG_L1 | STASIS_MSG_ERROR, "Metadata dump failed\n"); + } + + if (want_artifactory) { + if (globals.enable_artifactory) { + msg(STASIS_MSG_L1, "Uploading artifacts\n"); + delivery_artifact_upload(&ctx); + } else { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Artifact uploading is disabled\n"); + } + } else { + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Artifact uploading is disabled. deploy:artifactory is not configured\n"); + } + + msg(STASIS_MSG_L1, "Cleaning up\n"); + delivery_free(&ctx); + globals_free(); + tpl_free(); + + msg(STASIS_MSG_L1, "Done!\n"); + return 0; +} + diff --git a/src/str.c b/src/str.c index 16088b6..6afbf73 100644 --- a/src/str.c +++ b/src/str.c @@ -115,7 +115,7 @@ char** split(char *_sptr, const char* delim, size_t max) pos = token - sptr; break; } - result[i] = calloc(OMC_BUFSIZ, sizeof(char)); + result[i] = calloc(STASIS_BUFSIZ, sizeof(char)); if (!result[i]) { return NULL; } @@ -125,7 +125,7 @@ char** split(char *_sptr, const char* delim, size_t max) // pos is non-zero when maximum split is reached if (pos) { // append the remaining string contents to array - result[i] = calloc(OMC_BUFSIZ, sizeof(char)); + result[i] = calloc(STASIS_BUFSIZ, sizeof(char)); if (!result[i]) { return NULL; } @@ -319,13 +319,13 @@ void strsort(char **arr, unsigned int sort_mode) { // Default mode is alphabetic sort compar fn = _strsort_alpha_compare; - if (sort_mode == OMC_SORT_LEN_DESCENDING) { + if (sort_mode == STASIS_SORT_LEN_DESCENDING) { fn = _strsort_dsc_compare; - } else if (sort_mode == OMC_SORT_LEN_ASCENDING) { + } else if (sort_mode == STASIS_SORT_LEN_ASCENDING) { fn = _strsort_asc_compare; - } else if (sort_mode == OMC_SORT_ALPHA) { + } else if (sort_mode == STASIS_SORT_ALPHA) { fn = _strsort_alpha_compare; // ^ still selectable though ^ - } else if (sort_mode == OMC_SORT_NUMERIC) { + } else if (sort_mode == STASIS_SORT_NUMERIC) { fn = _strsort_numeric_compare; } diff --git a/src/system.c b/src/system.c index d5e77ce..526f0ec 100644 --- a/src/system.c +++ b/src/system.c @@ -1,5 +1,5 @@ #include "system.h" -#include "omc.h" +#include "core.h" int shell(struct Process *proc, char *args) { struct Process selfproc; @@ -106,7 +106,7 @@ int shell_safe(struct Process *proc, char *args) { char buf[1024] = {0}; int result; - char *invalid_ch = strpbrk(args, OMC_SHELL_SAFE_RESTRICT); + char *invalid_ch = strpbrk(args, STASIS_SHELL_SAFE_RESTRICT); if (invalid_ch) { args = NULL; } @@ -138,10 +138,10 @@ int shell_safe(struct Process *proc, char *args) { } char *shell_output(const char *command, int *status) { - const size_t initial_size = OMC_BUFSIZ; + const size_t initial_size = STASIS_BUFSIZ; size_t current_size = initial_size; char *result = NULL; - char line[OMC_BUFSIZ]; + char line[STASIS_BUFSIZ]; FILE *pp; errno = 0; diff --git a/src/template.c b/src/template.c index 0a57232..819fe92 100644 --- a/src/template.c +++ b/src/template.c @@ -216,7 +216,7 @@ char *tpl_render(char *str) { char *env_val = getenv(key); value = strdup(env_val ? env_val : ""); } else if (do_func) { // {{ func:NAME(a, ...) }} - char func_name_temp[OMC_NAME_MAX] = {0}; + char func_name_temp[STASIS_NAME_MAX] = {0}; strcpy(func_name_temp, type_stop + 1); char *param_begin = strchr(func_name_temp, '('); if (!param_begin) { diff --git a/src/utils.c b/src/utils.c index 86622ad..fb22f59 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,7 +1,7 @@ #include -#include "omc.h" +#include "core.h" -char *dirstack[OMC_DIRSTACK_MAX]; +char *dirstack[STASIS_DIRSTACK_MAX]; const ssize_t dirstack_max = sizeof(dirstack) / sizeof(dirstack[0]); ssize_t dirstack_len = 0; @@ -186,8 +186,8 @@ char **file_readlines(const char *filename, size_t start, size_t limit, ReaderFn } // Allocate buffer - if ((buffer = calloc(OMC_BUFSIZ, sizeof(char))) == NULL) { - SYSERROR("unable to allocate %d bytes for buffer", OMC_BUFSIZ); + if ((buffer = calloc(STASIS_BUFSIZ, sizeof(char))) == NULL) { + SYSERROR("unable to allocate %d bytes for buffer", STASIS_BUFSIZ); if (!use_stdin) { fclose(fp); } @@ -195,7 +195,7 @@ char **file_readlines(const char *filename, size_t start, size_t limit, ReaderFn } // count number the of lines in the file - while ((fgets(buffer, OMC_BUFSIZ - 1, fp)) != NULL) { + while ((fgets(buffer, STASIS_BUFSIZ - 1, fp)) != NULL) { lines++; } @@ -232,7 +232,7 @@ char **file_readlines(const char *filename, size_t start, size_t limit, ReaderFn continue; } - if (fgets(buffer, OMC_BUFSIZ - 1, fp) == NULL) { + if (fgets(buffer, STASIS_BUFSIZ - 1, fp) == NULL) { break; } @@ -249,7 +249,7 @@ char **file_readlines(const char *filename, size_t start, size_t limit, ReaderFn } } result[i] = strdup(buffer); - memset(buffer, '\0', OMC_BUFSIZ); + memset(buffer, '\0', STASIS_BUFSIZ); } guard_free(buffer); @@ -396,12 +396,12 @@ void msg(unsigned type, char *fmt, ...) { char header[255]; char status[20]; - if (type & OMC_MSG_NOP) { + if (type & STASIS_MSG_NOP) { // quiet mode return; } - if (!globals.verbose && type & OMC_MSG_RESTRICT) { + if (!globals.verbose && type & STASIS_MSG_RESTRICT) { // Verbose mode is not active return; } @@ -413,39 +413,39 @@ void msg(unsigned type, char *fmt, ...) { va_start(args, fmt); stream = stdout; - fprintf(stream, "%s", OMC_COLOR_RESET); - if (type & OMC_MSG_ERROR) { + fprintf(stream, "%s", STASIS_COLOR_RESET); + if (type & STASIS_MSG_ERROR) { // for error output stream = stderr; - fprintf(stream, "%s", OMC_COLOR_RED); + fprintf(stream, "%s", STASIS_COLOR_RED); strcpy(status, " ERROR: "); - } else if (type & OMC_MSG_WARN) { + } else if (type & STASIS_MSG_WARN) { stream = stderr; - fprintf(stream, "%s", OMC_COLOR_YELLOW); + fprintf(stream, "%s", STASIS_COLOR_YELLOW); strcpy(status, " WARNING: "); } else { - fprintf(stream, "%s", OMC_COLOR_GREEN); + fprintf(stream, "%s", STASIS_COLOR_GREEN); strcpy(status, " "); } - if (type & OMC_MSG_L1) { - sprintf(header, "==>%s" OMC_COLOR_RESET OMC_COLOR_WHITE, status); - } else if (type & OMC_MSG_L2) { - sprintf(header, " ->%s" OMC_COLOR_RESET, status); - } else if (type & OMC_MSG_L3) { - sprintf(header, OMC_COLOR_BLUE " ->%s" OMC_COLOR_RESET, status); + if (type & STASIS_MSG_L1) { + sprintf(header, "==>%s" STASIS_COLOR_RESET STASIS_COLOR_WHITE, status); + } else if (type & STASIS_MSG_L2) { + sprintf(header, " ->%s" STASIS_COLOR_RESET, status); + } else if (type & STASIS_MSG_L3) { + sprintf(header, STASIS_COLOR_BLUE " ->%s" STASIS_COLOR_RESET, status); } fprintf(stream, "%s", header); vfprintf(stream, fmt, args); - fprintf(stream, "%s", OMC_COLOR_RESET); + fprintf(stream, "%s", STASIS_COLOR_RESET); va_end(args); } void debug_shell() { - msg(OMC_MSG_L1 | OMC_MSG_WARN, "ENTERING OMC DEBUG SHELL\n" OMC_COLOR_RESET); - system("/bin/bash -c 'PS1=\"(OMC DEBUG) \\W $ \" bash --norc --noprofile'"); - msg(OMC_MSG_L1 | OMC_MSG_WARN, "EXITING OMC DEBUG SHELL\n" OMC_COLOR_RESET); + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "ENTERING STASIS DEBUG SHELL\n" STASIS_COLOR_RESET); + system("/bin/bash -c 'PS1=\"(STASIS DEBUG) \\W $ \" bash --norc --noprofile'"); + msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "EXITING STASIS DEBUG SHELL\n" STASIS_COLOR_RESET); exit(255); } @@ -460,7 +460,7 @@ char *xmkstemp(FILE **fp, const char *mode) { strcpy(tmpdir, "/tmp"); } memset(t_name, 0, sizeof(t_name)); - sprintf(t_name, "%s/%s", tmpdir, "OMC.XXXXXX"); + sprintf(t_name, "%s/%s", tmpdir, "STASIS.XXXXXX"); fd = mkstemp(t_name); *fp = fdopen(fd, mode); diff --git a/stasis.ini b/stasis.ini new file mode 100644 index 0000000..00fd3f4 --- /dev/null +++ b/stasis.ini @@ -0,0 +1,36 @@ +; (bool) Keep going even if a test fails +continue_on_error = false + +; (bool) Update all packages in the base to the latest release +always_update_base_environment = false + +; (bool) Remove conda installation during initialization +; true = reinstall conda +; false = do not reinstall conda +conda_fresh_start = true + +; (string) Install conda in a custom prefix +; DEFAULT: Conda will be installed under stasis/conda +; NOTE: conda_fresh_start will automatically be set to "false" +;conda_install_prefix = /path/to/conda + +; (list) Conda packages to be installed/overridden in the base environment +conda_packages = + conda>=23.7.0 + conda-build>=3.22.0 + boa + conda-verify + conda-libmamba-solver + +; (list) Python packages to be installed/overridden in the base environment +;pip_packages = + +[jfrog_cli_download] +url = https://releases.jfrog.io/artifactory +product = jfrog-cli +version_series = v2-jf +version = [RELEASE] +filename = jf + +[deploy:artifactory] + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5b45d58..f3a45e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,7 +21,7 @@ foreach(file ${files}) elseif (CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_compile_options(${file_without_ext} PRIVATE ${win_cflags} ${win_msvc_cflags}) endif() - target_link_libraries(${file_without_ext} PRIVATE ohmycal) + target_link_libraries(${file_without_ext} PRIVATE stasis_core) add_test(${file_without_ext} ${file_without_ext}) set_tests_properties(${file_without_ext} PROPERTIES diff --git a/tests/test_docker.c b/tests/test_docker.c index eac385e..04a73aa 100644 --- a/tests/test_docker.c +++ b/tests/test_docker.c @@ -4,16 +4,16 @@ struct DockerCapabilities cap_suite; void test_docker_capable() { struct DockerCapabilities cap; int result = docker_capable(&cap); - OMC_ASSERT(result == true, "docker installation unusable"); - OMC_ASSERT(cap.available, "docker is not available"); - OMC_ASSERT(cap.build != 0, "docker cannot build images"); + STASIS_ASSERT(result == true, "docker installation unusable"); + STASIS_ASSERT(cap.available, "docker is not available"); + STASIS_ASSERT(cap.build != 0, "docker cannot build images"); // no cap.podman assertion. doesn't matter if we have docker or podman - OMC_ASSERT(cap.usable, "docker is unusable"); + STASIS_ASSERT(cap.usable, "docker is unusable"); } void test_docker_exec() { - OMC_ASSERT(docker_exec("system info", 0) == 0, "unable to print docker information"); - OMC_ASSERT(docker_exec("system info", OMC_DOCKER_QUIET) == 0, "unable to be quiet"); + STASIS_ASSERT(docker_exec("system info", 0) == 0, "unable to print docker information"); + STASIS_ASSERT(docker_exec("system info", STASIS_DOCKER_QUIET) == 0, "unable to be quiet"); } void test_docker_sanitize_tag() { @@ -27,47 +27,47 @@ void test_docker_sanitize_tag() { break; } } - OMC_ASSERT(result == 0, "proper tag characters were not replaced correctly"); + STASIS_ASSERT(result == 0, "proper tag characters were not replaced correctly"); guard_free(input); } void test_docker_build_and_script_and_save() { - OMC_SKIP_IF(docker_exec("pull alpine:latest", OMC_DOCKER_QUIET), "unable to pull an image"); + STASIS_SKIP_IF(docker_exec("pull alpine:latest", STASIS_DOCKER_QUIET), "unable to pull an image"); const char *dockerfile_contents = "FROM alpine:latest\nCMD [\"sh\", \"-l\"]\n"; mkdir("test_docker_build", 0755); if (!pushd("test_docker_build")) { - omc_testing_write_ascii("Dockerfile", dockerfile_contents); - OMC_ASSERT(docker_build(".", "-t test_docker_build", cap_suite.build) == 0, "docker build test failed"); - OMC_ASSERT(docker_script("test_docker_build", "uname -a", 0) == 0, "simple docker container script execution failed"); - OMC_ASSERT(docker_save("test_docker_build", ".", OMC_DOCKER_IMAGE_COMPRESSION) == 0, "saving a simple image failed"); - OMC_ASSERT(docker_exec("load < test_docker_build.tar.*", 0) == 0, "loading a simple image failed"); + stasis_testing_write_ascii("Dockerfile", dockerfile_contents); + STASIS_ASSERT(docker_build(".", "-t test_docker_build", cap_suite.build) == 0, "docker build test failed"); + STASIS_ASSERT(docker_script("test_docker_build", "uname -a", 0) == 0, "simple docker container script execution failed"); + STASIS_ASSERT(docker_save("test_docker_build", ".", STASIS_DOCKER_IMAGE_COMPRESSION) == 0, "saving a simple image failed"); + STASIS_ASSERT(docker_exec("load < test_docker_build.tar.*", 0) == 0, "loading a simple image failed"); docker_exec("image rm -f test_docker_build", 0); remove("Dockerfile"); popd(); remove("test_docker_build"); } else { - OMC_ASSERT(false, "dir change failed"); + STASIS_ASSERT(false, "dir change failed"); return; } } void test_docker_validate_compression_program() { - OMC_ASSERT(docker_validate_compression_program(OMC_DOCKER_IMAGE_COMPRESSION) == 0, "baked-in compression program does not exist"); + STASIS_ASSERT(docker_validate_compression_program(STASIS_DOCKER_IMAGE_COMPRESSION) == 0, "baked-in compression program does not exist"); } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); + STASIS_TEST_BEGIN_MAIN(); if (!docker_capable(&cap_suite)) { - return OMC_TEST_SUITE_SKIP; + return STASIS_TEST_SUITE_SKIP; } - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_FUNC *tests[] = { test_docker_capable, test_docker_exec, test_docker_build_and_script_and_save, test_docker_sanitize_tag, test_docker_validate_compression_program, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/test_ini.c b/tests/test_ini.c index f9f8234..5958cfd 100644 --- a/tests/test_ini.c +++ b/tests/test_ini.c @@ -4,22 +4,22 @@ void test_ini_open_empty() { struct INIFILE *ini; ini = ini_open("/dev/null"); - OMC_ASSERT(ini->section_count == 1, "should have at least one section pre-allocated"); - OMC_ASSERT(ini->section != NULL, "default section not present"); + STASIS_ASSERT(ini->section_count == 1, "should have at least one section pre-allocated"); + STASIS_ASSERT(ini->section != NULL, "default section not present"); ini_free(&ini); - OMC_ASSERT(ini == NULL, "ini_free did not set pointer argument to NULL"); + STASIS_ASSERT(ini == NULL, "ini_free did not set pointer argument to NULL"); } void test_ini_open() { const char *filename = "ini_open.ini"; const char *data = "[default]\na=1\nb=2\nc=3\n[section name]test=true\n"; struct INIFILE *ini; - omc_testing_write_ascii(filename, data); + stasis_testing_write_ascii(filename, data); ini = ini_open(filename); - OMC_ASSERT(ini->section_count == 2, "should have two sections"); - OMC_ASSERT(ini->section != NULL, "sections are not allocated"); + STASIS_ASSERT(ini->section_count == 2, "should have two sections"); + STASIS_ASSERT(ini->section != NULL, "sections are not allocated"); ini_free(&ini); - OMC_ASSERT(ini == NULL, "ini_free did not set pointer argument to NULL"); + STASIS_ASSERT(ini == NULL, "ini_free did not set pointer argument to NULL"); remove(filename); } @@ -29,27 +29,27 @@ void test_ini_section_search() { struct INIFILE *ini; struct INISection *section; - omc_testing_write_ascii(filename, data); + stasis_testing_write_ascii(filename, data); ini = ini_open(filename); section = ini_section_search(&ini, INI_SEARCH_BEGINS, "section"); - OMC_ASSERT(section->data_count == 1, "should have one data variable"); - OMC_ASSERT(strcmp(section->data[0]->key, "test") == 0, "should have one data variable named 'test'"); - OMC_ASSERT(strcmp(section->data[0]->value, "true") == 0, "'test' should be equal to 'true'"); - OMC_ASSERT(strcmp(section->key, "section name here") == 0, "defined section not found"); + STASIS_ASSERT(section->data_count == 1, "should have one data variable"); + STASIS_ASSERT(strcmp(section->data[0]->key, "test") == 0, "should have one data variable named 'test'"); + STASIS_ASSERT(strcmp(section->data[0]->value, "true") == 0, "'test' should be equal to 'true'"); + STASIS_ASSERT(strcmp(section->key, "section name here") == 0, "defined section not found"); section = ini_section_search(&ini, INI_SEARCH_SUBSTR, "name"); - OMC_ASSERT(strcmp(section->data[0]->key, "test") == 0, "should have one data variable named 'test'"); - OMC_ASSERT(strcmp(section->data[0]->value, "true") == 0, "'test' should be equal to 'true'"); - OMC_ASSERT(strcmp(section->key, "section name here") == 0, "defined section not found"); + STASIS_ASSERT(strcmp(section->data[0]->key, "test") == 0, "should have one data variable named 'test'"); + STASIS_ASSERT(strcmp(section->data[0]->value, "true") == 0, "'test' should be equal to 'true'"); + STASIS_ASSERT(strcmp(section->key, "section name here") == 0, "defined section not found"); section = ini_section_search(&ini, INI_SEARCH_EXACT, "section name here"); - OMC_ASSERT(strcmp(section->data[0]->key, "test") == 0, "should have one data variable named 'test'"); - OMC_ASSERT(strcmp(section->data[0]->value, "true") == 0, "'test' should be equal to 'true'"); - OMC_ASSERT(strcmp(section->key, "section name here") == 0, "defined section not found"); + STASIS_ASSERT(strcmp(section->data[0]->key, "test") == 0, "should have one data variable named 'test'"); + STASIS_ASSERT(strcmp(section->data[0]->value, "true") == 0, "'test' should be equal to 'true'"); + STASIS_ASSERT(strcmp(section->key, "section name here") == 0, "defined section not found"); section = ini_section_search(&ini, INI_SEARCH_BEGINS, "bogus"); - OMC_ASSERT(section == NULL, "bogus section search returned non-NULL"); + STASIS_ASSERT(section == NULL, "bogus section search returned non-NULL"); section = ini_section_search(&ini, INI_SEARCH_BEGINS, NULL); - OMC_ASSERT(section == NULL, "NULL argument returned non-NULL"); + STASIS_ASSERT(section == NULL, "NULL argument returned non-NULL"); ini_free(&ini); remove(filename); } @@ -59,15 +59,15 @@ void test_ini_has_key() { const char *data = "[default]\na=1\nb=2\nc=3\n[section name here]\ntest=true\n"; struct INIFILE *ini; - omc_testing_write_ascii(filename, data); + stasis_testing_write_ascii(filename, data); ini = ini_open(filename); - OMC_ASSERT(ini_has_key(ini, "default", "a") == true, "'default:a' key should exist"); - OMC_ASSERT(ini_has_key(NULL, "default", NULL) == false, "NULL ini object should return false"); - OMC_ASSERT(ini_has_key(ini, "default", NULL) == false, "NULL key should return false"); - OMC_ASSERT(ini_has_key(ini, NULL, "a") == false, "NULL section should return false"); - OMC_ASSERT(ini_has_key(ini, "default", "noname") == false, "'default:noname' key should not exist"); - OMC_ASSERT(ini_has_key(ini, "doesnotexist", "name") == false, "'doesnotexist:name' key should not exist"); + STASIS_ASSERT(ini_has_key(ini, "default", "a") == true, "'default:a' key should exist"); + STASIS_ASSERT(ini_has_key(NULL, "default", NULL) == false, "NULL ini object should return false"); + STASIS_ASSERT(ini_has_key(ini, "default", NULL) == false, "NULL key should return false"); + STASIS_ASSERT(ini_has_key(ini, NULL, "a") == false, "NULL section should return false"); + STASIS_ASSERT(ini_has_key(ini, "default", "noname") == false, "'default:noname' key should not exist"); + STASIS_ASSERT(ini_has_key(ini, "doesnotexist", "name") == false, "'doesnotexist:name' key should not exist"); ini_free(&ini); remove(filename); } @@ -77,32 +77,32 @@ void test_ini_setval_getval() { const char *data = "[default]\na=1\nb=2\nc=3\n[section name here]\ntest=true\n"; struct INIFILE *ini; - omc_testing_write_ascii(filename, data); + stasis_testing_write_ascii(filename, data); ini = ini_open(filename); union INIVal val; - OMC_ASSERT(ini_setval(&ini, INI_SETVAL_REPLACE, "default", "a", "changed") == 0, "failed to set value"); - OMC_ASSERT(ini_getval(ini, "default", "a", INIVAL_TYPE_STR, &val) == 0, "failed to get value"); - OMC_ASSERT(strcmp(val.as_char_p, "a") != 0, "unexpected value loaded from modified variable"); - OMC_ASSERT(strcmp(val.as_char_p, "changed") == 0, "unexpected value loaded from modified variable"); + STASIS_ASSERT(ini_setval(&ini, INI_SETVAL_REPLACE, "default", "a", "changed") == 0, "failed to set value"); + STASIS_ASSERT(ini_getval(ini, "default", "a", INIVAL_TYPE_STR, &val) == 0, "failed to get value"); + STASIS_ASSERT(strcmp(val.as_char_p, "a") != 0, "unexpected value loaded from modified variable"); + STASIS_ASSERT(strcmp(val.as_char_p, "changed") == 0, "unexpected value loaded from modified variable"); - OMC_ASSERT(ini_setval(&ini, INI_SETVAL_APPEND, "default", "a", " twice") == 0, "failed to set value"); - OMC_ASSERT(ini_getval(ini, "default", "a", INIVAL_TYPE_STR, &val) == 0, "failed to get value"); - OMC_ASSERT(strcmp(val.as_char_p, "changed") != 0, "unexpected value loaded from modified variable"); - OMC_ASSERT(strcmp(val.as_char_p, "changed twice") == 0, "unexpected value loaded from modified variable"); + STASIS_ASSERT(ini_setval(&ini, INI_SETVAL_APPEND, "default", "a", " twice") == 0, "failed to set value"); + STASIS_ASSERT(ini_getval(ini, "default", "a", INIVAL_TYPE_STR, &val) == 0, "failed to get value"); + STASIS_ASSERT(strcmp(val.as_char_p, "changed") != 0, "unexpected value loaded from modified variable"); + STASIS_ASSERT(strcmp(val.as_char_p, "changed twice") == 0, "unexpected value loaded from modified variable"); ini_free(&ini); remove(filename); } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_ini_open_empty, test_ini_open, test_ini_section_search, test_ini_has_key, test_ini_setval_getval, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/test_relocation.c b/tests/test_relocation.c index 0796074..a6c33f2 100644 --- a/tests/test_relocation.c +++ b/tests/test_relocation.c @@ -16,8 +16,8 @@ void test_replace_text() { char input[BUFSIZ] = {0}; strcpy(input, test_string); - OMC_ASSERT(replace_text(input, target, "^^^", 0) == 0, "string replacement failed"); - OMC_ASSERT(strcmp(input, expected) == 0, "unexpected replacement"); + STASIS_ASSERT(replace_text(input, target, "^^^", 0) == 0, "string replacement failed"); + STASIS_ASSERT(strcmp(input, expected) == 0, "unexpected replacement"); } } @@ -33,9 +33,9 @@ void test_file_replace_text() { if (fp) { fprintf(fp, "%s", test_string); fclose(fp); - OMC_ASSERT(file_replace_text(filename, target, "^^^", 0) == 0, "string replacement failed"); + STASIS_ASSERT(file_replace_text(filename, target, "^^^", 0) == 0, "string replacement failed"); } else { - OMC_ASSERT(false, "failed to open file for writing"); + STASIS_ASSERT(false, "failed to open file for writing"); return; } @@ -43,20 +43,20 @@ void test_file_replace_text() { fp = fopen(filename, "r"); if (fp) { fread(input, sizeof(*input), sizeof(input), fp); - OMC_ASSERT(strcmp(input, expected) == 0, "unexpected replacement"); + STASIS_ASSERT(strcmp(input, expected) == 0, "unexpected replacement"); } else { - OMC_ASSERT(false, "failed to open file for reading"); + STASIS_ASSERT(false, "failed to open file for reading"); return; } } } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_replace_text, test_file_replace_text, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/test_str.c b/tests/test_str.c index fc2cf46..be3f3e1 100644 --- a/tests/test_str.c +++ b/tests/test_str.c @@ -15,8 +15,8 @@ void test_to_short_version() { for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *result = to_short_version(tc[i].data); - OMC_ASSERT_FATAL(result != NULL, "should not be NULL"); - OMC_ASSERT(strcmp(result, tc[i].expected) == 0, "unexpected result"); + STASIS_ASSERT_FATAL(result != NULL, "should not be NULL"); + STASIS_ASSERT(strcmp(result, tc[i].expected) == 0, "unexpected result"); guard_free(result); } @@ -38,7 +38,7 @@ void test_tolower_s() { char input[100] = {0}; strcpy(input, tc[i].data); tolower_s(input); - OMC_ASSERT(strcmp(input, tc[i].expected) == 0, "unexpected result"); + STASIS_ASSERT(strcmp(input, tc[i].expected) == 0, "unexpected result"); } } @@ -59,7 +59,7 @@ void test_isdigit_s() { }; for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { - OMC_ASSERT(isdigit_s(tc[i].data) == tc[i].expected, "unexpected result"); + STASIS_ASSERT(isdigit_s(tc[i].data) == tc[i].expected, "unexpected result"); } } @@ -75,10 +75,10 @@ void test_strdup_array_and_strcmp_array() { .expected = (const char *[]) {"I", "have", "a", "pencil", "box", NULL}}, }; - OMC_ASSERT(strdup_array(NULL) == NULL, "returned non-NULL on NULL argument"); + STASIS_ASSERT(strdup_array(NULL) == NULL, "returned non-NULL on NULL argument"); for (size_t outer = 0; outer < sizeof(tc) / sizeof(*tc); outer++) { char **result = strdup_array((char **) tc[outer].data); - OMC_ASSERT(strcmp_array((const char **) result, tc[outer].expected) == 0, "array members were different"); + STASIS_ASSERT(strcmp_array((const char **) result, tc[outer].expected) == 0, "array members were different"); } const struct testcase tc_bad[] = { @@ -88,12 +88,12 @@ void test_strdup_array_and_strcmp_array() { .expected = (const char *[]) {"I", "have", "a", "pencil", "box", NULL}}, }; - OMC_ASSERT(strcmp_array(tc[0].data, NULL) > 0, "right argument is NULL, expected positive return"); - OMC_ASSERT(strcmp_array(NULL, tc[0].data) < 0, "left argument is NULL, expected negative return"); - OMC_ASSERT(strcmp_array(NULL, NULL) == 0, "left and right arguments are NULL, expected zero return"); + STASIS_ASSERT(strcmp_array(tc[0].data, NULL) > 0, "right argument is NULL, expected positive return"); + STASIS_ASSERT(strcmp_array(NULL, tc[0].data) < 0, "left argument is NULL, expected negative return"); + STASIS_ASSERT(strcmp_array(NULL, NULL) == 0, "left and right arguments are NULL, expected zero return"); for (size_t outer = 0; outer < sizeof(tc_bad) / sizeof(*tc_bad); outer++) { char **result = strdup_array((char **) tc_bad[outer].data); - OMC_ASSERT(strcmp_array((const char **) result, tc_bad[outer].expected) != 0, "array members were identical"); + STASIS_ASSERT(strcmp_array((const char **) result, tc_bad[outer].expected) != 0, "array members were identical"); } } @@ -112,7 +112,7 @@ void test_strchrdel() { for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *data = strdup(tc[i].data); strchrdel(data, tc[i].input); - OMC_ASSERT(strcmp(data, tc[i].expected) == 0, "wrong status for condition"); + STASIS_ASSERT(strcmp(data, tc[i].expected) == 0, "wrong status for condition"); guard_free(data); } } @@ -138,7 +138,7 @@ void test_endswith() { }; for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { int result = endswith(tc[i].data, tc[i].input); - OMC_ASSERT(result == tc[i].expected, "wrong status for condition"); + STASIS_ASSERT(result == tc[i].expected, "wrong status for condition"); } } @@ -163,7 +163,7 @@ void test_startswith() { }; for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { int result = startswith(tc[i].data, tc[i].input); - OMC_ASSERT(result == tc[i].expected, "wrong status for condition"); + STASIS_ASSERT(result == tc[i].expected, "wrong status for condition"); } } @@ -180,7 +180,7 @@ void test_num_chars() { {.data = "abc\t\ndef\nabc\ndef\n", .input = '\t', .expected = 1}, }; for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { - OMC_ASSERT(num_chars(tc[i].data, tc[i].input) == tc[i].expected, "incorrect number of characters detected"); + STASIS_ASSERT(num_chars(tc[i].data, tc[i].input) == tc[i].expected, "incorrect number of characters detected"); } } @@ -204,7 +204,7 @@ void test_split() { for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char **result; result = split(tc[i].data, tc[i].delim, tc[i].max_split); - OMC_ASSERT(strcmp_array(result, tc[i].expected) == 0, "Split failed"); + STASIS_ASSERT(strcmp_array(result, tc[i].expected) == 0, "Split failed"); GENERIC_ARRAY_FREE(result); } } @@ -224,7 +224,7 @@ void test_join() { for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *result; result = join(tc[i].data, tc[i].delim); - OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array"); + STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array"); guard_free(result); } } @@ -243,7 +243,7 @@ void test_join_ex() { for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *result; result = join_ex(tc[i].delim, "a", "b", "c", "d", "e", NULL); - OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array"); + STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array"); guard_free(result); } } @@ -269,7 +269,7 @@ void test_substring_between() { }; for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *result = substring_between(tc[i].data, tc[i].delim); - OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "unable to extract substring"); + STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "unable to extract substring"); guard_free(result); } } @@ -288,7 +288,7 @@ void test_strdeldup() { }; for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char **result = strdeldup(tc[i].data); - OMC_ASSERT(strcmp_array(result, tc[i].expected) == 0, "incorrect number of duplicates removed"); + STASIS_ASSERT(strcmp_array(result, tc[i].expected) == 0, "incorrect number of duplicates removed"); GENERIC_ARRAY_FREE(result); } } @@ -313,13 +313,13 @@ void test_lstrip() { {.data = " I am a string.\n", .expected = "I am a string.\n"}, }; - OMC_ASSERT(lstrip(NULL) == NULL, "incorrect return type"); + STASIS_ASSERT(lstrip(NULL) == NULL, "incorrect return type"); for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *buf = calloc(255, sizeof(*buf)); char *result; strcpy(buf, tc[i].data); result = lstrip(buf); - OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-left"); + STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-left"); guard_free(buf); } } @@ -338,13 +338,13 @@ void test_strip() { {.data = " ", .expected = ""}, }; - OMC_ASSERT(strip(NULL) == NULL, "incorrect return type"); + STASIS_ASSERT(strip(NULL) == NULL, "incorrect return type"); for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { char *buf = calloc(255, sizeof(*buf)); char *result; strcpy(buf, tc[i].data); result = strip(buf); - OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-right"); + STASIS_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-right"); guard_free(buf); } } @@ -354,8 +354,8 @@ void test_isempty() { } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_to_short_version, test_tolower_s, test_isdigit_s, @@ -372,6 +372,6 @@ int main(int argc, char *argv[]) { test_join_ex, test_substring_between, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/test_strlist.c b/tests/test_strlist.c index aa9eb4e..723c904 100644 --- a/tests/test_strlist.c +++ b/tests/test_strlist.c @@ -8,7 +8,7 @@ do { \ strlist_append(&list, (char *) tc[i].data); \ result = FUNC(list, i); \ if (!strlist_errno) { \ - OMC_ASSERT(result == (TYPE) tc[i].expected, "unexpected numeric result"); \ + STASIS_ASSERT(result == (TYPE) tc[i].expected, "unexpected numeric result"); \ } \ } \ guard_strlist_free(&list); \ @@ -17,9 +17,9 @@ do { \ void test_strlist_init() { struct StrList *list; list = strlist_init(); - OMC_ASSERT(list != NULL, "list should not be NULL"); - OMC_ASSERT(list->num_alloc == 1, "fresh list should have one record allocated"); - OMC_ASSERT(list->num_inuse == 0, "fresh list should have no records in use"); + STASIS_ASSERT(list != NULL, "list should not be NULL"); + STASIS_ASSERT(list->num_alloc == 1, "fresh list should have one record allocated"); + STASIS_ASSERT(list->num_inuse == 0, "fresh list should have no records in use"); guard_strlist_free(&list); } @@ -47,9 +47,9 @@ void test_strlist_append() { list = strlist_init(); for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { strlist_append(&list, (char *) tc[i].data); - OMC_ASSERT(list->num_inuse == tc[i].expected_in_use, "incorrect number of records in use"); - OMC_ASSERT(list->num_alloc == tc[i].expected_in_use + 1, "incorrect number of records allocated"); - OMC_ASSERT(strcmp(strlist_item(list, i), tc[i].data) == 0, "value was appended incorrectly. data mismatch."); + STASIS_ASSERT(list->num_inuse == tc[i].expected_in_use, "incorrect number of records in use"); + STASIS_ASSERT(list->num_alloc == tc[i].expected_in_use + 1, "incorrect number of records allocated"); + STASIS_ASSERT(strcmp(strlist_item(list, i), tc[i].data) == 0, "value was appended incorrectly. data mismatch."); } guard_strlist_free(&list); } @@ -62,7 +62,7 @@ void test_strlist_append_many_records() { for (size_t i = 0; i < maxrec; i++) { strlist_append(&list, (char *) data); } - OMC_ASSERT(strlist_count(list) == maxrec, "record count mismatch"); + STASIS_ASSERT(strlist_count(list) == maxrec, "record count mismatch"); guard_strlist_free(&list); } @@ -86,7 +86,7 @@ void test_strlist_set() { result = strcmp(strlist_item(list, i), replacement_long) == 0; set_resize_long_all_ok += result; } - OMC_ASSERT(set_resize_long_all_ok == (int) maxrec, "Replacing record with longer strings failed"); + STASIS_ASSERT(set_resize_long_all_ok == (int) maxrec, "Replacing record with longer strings failed"); for (size_t i = 0; i < maxrec; i++) { int result; @@ -94,9 +94,9 @@ void test_strlist_set() { result = strcmp(strlist_item(list, i), replacement_short) == 0; set_resize_short_all_ok += result; } - OMC_ASSERT(set_resize_short_all_ok == (int) maxrec, "Replacing record with shorter strings failed"); + STASIS_ASSERT(set_resize_short_all_ok == (int) maxrec, "Replacing record with shorter strings failed"); - OMC_ASSERT(strlist_count(list) == maxrec, "record count mismatch"); + STASIS_ASSERT(strlist_count(list) == maxrec, "record count mismatch"); guard_strlist_free(&list); } @@ -115,14 +115,14 @@ void test_strlist_append_file() { const char *local_filename = "test_strlist_append_file.txt"; struct testcase tc[] = { - {.origin = "https://ssb.stsci.edu/jhunk/omc_test/test_strlist_append_file_from_remote.txt", .expected = expected}, + {.origin = "https://ssb.stsci.edu/jhunk/stasis_test/test_strlist_append_file_from_remote.txt", .expected = expected}, {.origin = local_filename, .expected = expected}, }; FILE *fp; fp = fopen(local_filename, "w"); if (!fp) { - OMC_ASSERT(false, "unable to open local file for writing"); + STASIS_ASSERT(false, "unable to open local file for writing"); } for (size_t i = 0; expected[i] != NULL; i++) { fprintf(fp, "%s", expected[i]); @@ -133,7 +133,7 @@ void test_strlist_append_file() { struct StrList *list; list = strlist_init(); if (!list) { - OMC_ASSERT(false, "failed to create list"); + STASIS_ASSERT(false, "failed to create list"); return; } strlist_append_file(list, (char *) tc[i].origin, NULL); @@ -142,9 +142,9 @@ void test_strlist_append_file() { const char *right; left = strlist_item(list, z); right = expected[z]; - OMC_ASSERT(strcmp(left, right) == 0, "file content is different than expected"); + STASIS_ASSERT(strcmp(left, right) == 0, "file content is different than expected"); } - OMC_ASSERT(strcmp_array(list->data, expected) == 0, "file contents does not match expected values"); + STASIS_ASSERT(strcmp_array(list->data, expected) == 0, "file contents does not match expected values"); guard_strlist_free(&list); } } @@ -159,10 +159,10 @@ void test_strlist_append_strlist() { strlist_append(&right, "B"); strlist_append(&right, "C"); strlist_append_strlist(left, right); - OMC_ASSERT(strlist_cmp(left, right) == 0, "left and right should be identical"); + STASIS_ASSERT(strlist_cmp(left, right) == 0, "left and right should be identical"); strlist_append_strlist(left, right); - OMC_ASSERT(strlist_cmp(left, right) == 1, "left and right should be different"); - OMC_ASSERT(strlist_count(left) > strlist_count(right), "left should be larger than right"); + STASIS_ASSERT(strlist_cmp(left, right) == 1, "left and right should be different"); + STASIS_ASSERT(strlist_count(left) > strlist_count(right), "left should be larger than right"); guard_strlist_free(&left); guard_strlist_free(&right); } @@ -182,7 +182,7 @@ void test_strlist_append_array() { char *item = strlist_item(list, i); result += strcmp(item, data[i]) == 0; } - OMC_ASSERT(result == strlist_count(list), "result is not identical to input"); + STASIS_ASSERT(result == strlist_count(list), "result is not identical to input"); guard_strlist_free(&list); } @@ -193,9 +193,9 @@ void test_strlist_append_tokenize() { list = strlist_init(); strlist_append_tokenize(list, (char *) data, "\n"); // note: the trailing '\n' in the data is parsed as a token - OMC_ASSERT(line_count + 1 == strlist_count(list), "unexpected number of lines in array"); + STASIS_ASSERT(line_count + 1 == strlist_count(list), "unexpected number of lines in array"); int trailing_item_is_empty = isempty(strlist_item(list, strlist_count(list) - 1)); - OMC_ASSERT(trailing_item_is_empty, "trailing record should be an empty string"); + STASIS_ASSERT(trailing_item_is_empty, "trailing record should be an empty string"); guard_strlist_free(&list); } @@ -207,10 +207,10 @@ void test_strlist_copy() { strlist_append(&list, "of this data"); strlist_append(&list, "1:1, please"); - OMC_ASSERT(strlist_copy(NULL) == NULL, "NULL argument should return NULL"); + STASIS_ASSERT(strlist_copy(NULL) == NULL, "NULL argument should return NULL"); list_copy = strlist_copy(list); - OMC_ASSERT(strlist_cmp(list, list_copy) == 0, "list was copied incorrectly"); + STASIS_ASSERT(strlist_cmp(list, list_copy) == 0, "list was copied incorrectly"); guard_strlist_free(&list); guard_strlist_free(&list_copy); } @@ -241,8 +241,8 @@ void test_strlist_remove() { } } size_t len_after = strlist_count(list); - OMC_ASSERT(len_before == data_size, "list length before modification is incorrect. new records?"); - OMC_ASSERT(len_after == (len_before - removed), "list length after modification is incorrect. not enough records removed."); + STASIS_ASSERT(len_before == data_size, "list length before modification is incorrect. new records?"); + STASIS_ASSERT(len_after == (len_before - removed), "list length after modification is incorrect. not enough records removed."); guard_strlist_free(&list); } @@ -253,18 +253,18 @@ void test_strlist_cmp() { left = strlist_init(); right = strlist_init(); - OMC_ASSERT(strlist_cmp(NULL, NULL) == -1, "NULL first argument should return -1"); - OMC_ASSERT(strlist_cmp(left, NULL) == -2, "NULL second argument should return -2"); - OMC_ASSERT(strlist_cmp(left, right) == 0, "should be the same"); + STASIS_ASSERT(strlist_cmp(NULL, NULL) == -1, "NULL first argument should return -1"); + STASIS_ASSERT(strlist_cmp(left, NULL) == -2, "NULL second argument should return -2"); + STASIS_ASSERT(strlist_cmp(left, right) == 0, "should be the same"); strlist_append(&left, "left"); - OMC_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); + STASIS_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); strlist_append(&right, "right"); - OMC_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); + STASIS_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); strlist_append(&left, "left again"); strlist_append(&left, "left again"); strlist_append(&left, "left again"); - OMC_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); - OMC_ASSERT(strlist_cmp(right, left) == 1, "should return 1"); + STASIS_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); + STASIS_ASSERT(strlist_cmp(right, left) == 1, "should return 1"); guard_strlist_free(&left); guard_strlist_free(&right); } @@ -286,7 +286,7 @@ void test_strlist_reverse() { char *item = strlist_item(list, i); result += strcmp(item, expected[i]) == 0; } - OMC_ASSERT(result == (int) strlist_count(list), "alphabetic sort failed"); + STASIS_ASSERT(result == (int) strlist_count(list), "alphabetic sort failed"); guard_strlist_free(&list); } @@ -297,8 +297,8 @@ void test_strlist_count() { strlist_append(&list, "123"); strlist_append(&list, "def"); strlist_append(&list, "456"); - OMC_ASSERT(strlist_count(NULL) == 0, "NULL input should produce a zero result"); - OMC_ASSERT(strlist_count(list) == 4, "incorrect record count"); + STASIS_ASSERT(strlist_count(NULL) == 0, "NULL input should produce a zero result"); + STASIS_ASSERT(strlist_count(list) == 4, "incorrect record count"); guard_strlist_free(&list); } @@ -314,13 +314,13 @@ void test_strlist_sort_alphabetic() { list = strlist_init(); strlist_append_array(list, (char **) data); - strlist_sort(list, OMC_SORT_ALPHA); + strlist_sort(list, STASIS_SORT_ALPHA); int result = 0; for (size_t i = 0; i < strlist_count(list); i++) { char *item = strlist_item(list, i); result += strcmp(item, expected[i]) == 0; } - OMC_ASSERT(result == (int) strlist_count(list), "alphabetic sort failed"); + STASIS_ASSERT(result == (int) strlist_count(list), "alphabetic sort failed"); guard_strlist_free(&list); } @@ -335,13 +335,13 @@ void test_strlist_sort_len_ascending() { list = strlist_init(); strlist_append_array(list, (char **) data); - strlist_sort(list, OMC_SORT_LEN_ASCENDING); + strlist_sort(list, STASIS_SORT_LEN_ASCENDING); int result = 0; for (size_t i = 0; i < strlist_count(list); i++) { char *item = strlist_item(list, i); result += strcmp(item, expected[i]) == 0; } - OMC_ASSERT(result == (int) strlist_count(list), "ascending sort failed"); + STASIS_ASSERT(result == (int) strlist_count(list), "ascending sort failed"); guard_strlist_free(&list); } @@ -356,13 +356,13 @@ void test_strlist_sort_len_descending() { list = strlist_init(); strlist_append_array(list, (char **) data); - strlist_sort(list, OMC_SORT_LEN_DESCENDING); + strlist_sort(list, STASIS_SORT_LEN_DESCENDING); int result = 0; for (size_t i = 0; i < strlist_count(list); i++) { char *item = strlist_item(list, i); result += strcmp(item, expected[i]) == 0; } - OMC_ASSERT(result == (int) strlist_count(list), "descending sort failed"); + STASIS_ASSERT(result == (int) strlist_count(list), "descending sort failed"); guard_strlist_free(&list); } @@ -370,7 +370,7 @@ void test_strlist_item_as_str() { struct StrList *list; list = strlist_init(); strlist_append(&list, "hello"); - OMC_ASSERT(strcmp(strlist_item_as_str(list, 0), "hello") == 0, "unexpected string result"); + STASIS_ASSERT(strcmp(strlist_item_as_str(list, 0), "hello") == 0, "unexpected string result"); guard_strlist_free(&list); } @@ -578,8 +578,8 @@ void test_strlist_item_as_long_double() { } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_strlist_init, test_strlist_free, test_strlist_append, @@ -612,6 +612,6 @@ int main(int argc, char *argv[]) { test_strlist_item_as_double, test_strlist_item_as_long_double, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/test_system.c b/tests/test_system.c index bf60222..0f6bfb7 100644 --- a/tests/test_system.c +++ b/tests/test_system.c @@ -2,7 +2,7 @@ static int ascii_file_contains(const char *filename, const char *value) { int result = -1; - char *contents = omc_testing_read_ascii(filename); + char *contents = stasis_testing_read_ascii(filename); if (!contents) { perror(filename); return result; @@ -16,71 +16,71 @@ void test_shell_output_null_args() { char *result; int status; result = shell_output(NULL, &status); - OMC_ASSERT(strcmp(result, "") == 0, "no output expected"); - OMC_ASSERT(status != 0, "expected a non-zero exit code due to null argument string"); + STASIS_ASSERT(strcmp(result, "") == 0, "no output expected"); + STASIS_ASSERT(status != 0, "expected a non-zero exit code due to null argument string"); } void test_shell_output_non_zero_exit() { char *result; int status; result = shell_output("HELLO1234 WORLD", &status); - OMC_ASSERT(strcmp(result, "") == 0, "no output expected"); - OMC_ASSERT(status != 0, "expected a non-zero exit code due to intentional error"); + STASIS_ASSERT(strcmp(result, "") == 0, "no output expected"); + STASIS_ASSERT(status != 0, "expected a non-zero exit code due to intentional error"); } void test_shell_output() { char *result; int status; result = shell_output("echo HELLO WORLD", &status); - OMC_ASSERT(strcmp(result, "HELLO WORLD\n") == 0, "output message was incorrect"); - OMC_ASSERT(status == 0, "expected zero exit code"); + STASIS_ASSERT(strcmp(result, "HELLO WORLD\n") == 0, "output message was incorrect"); + STASIS_ASSERT(status == 0, "expected zero exit code"); } void test_shell_safe() { struct Process proc; memset(&proc, 0, sizeof(proc)); shell_safe(&proc, "true"); - OMC_ASSERT(proc.returncode == 0, "expected a zero exit code"); + STASIS_ASSERT(proc.returncode == 0, "expected a zero exit code"); } void test_shell_safe_verify_restrictions() { struct Process proc; - const char *invalid_chars = OMC_SHELL_SAFE_RESTRICT; + const char *invalid_chars = STASIS_SHELL_SAFE_RESTRICT; for (size_t i = 0; i < strlen(invalid_chars); i++) { char cmd[PATH_MAX] = {0}; memset(&proc, 0, sizeof(proc)); sprintf(cmd, "true%c false", invalid_chars[i]); shell_safe(&proc, cmd); - OMC_ASSERT(proc.returncode == -1, "expected a negative result due to intentional error"); + STASIS_ASSERT(proc.returncode == -1, "expected a negative result due to intentional error"); } } void test_shell_null_proc() { int returncode = shell(NULL, "true"); - OMC_ASSERT(returncode == 0, "expected a zero exit code"); + STASIS_ASSERT(returncode == 0, "expected a zero exit code"); } void test_shell_null_args() { struct Process proc; memset(&proc, 0, sizeof(proc)); shell(&proc, NULL); - OMC_ASSERT(proc.returncode < 0, "expected a non-zero/negative exit code"); + STASIS_ASSERT(proc.returncode < 0, "expected a non-zero/negative exit code"); } void test_shell_exit() { struct Process proc; memset(&proc, 0, sizeof(proc)); shell(&proc, "true"); - OMC_ASSERT(proc.returncode == 0, "expected a zero exit code"); + STASIS_ASSERT(proc.returncode == 0, "expected a zero exit code"); } void test_shell_non_zero_exit() { struct Process proc; memset(&proc, 0, sizeof(proc)); shell(&proc, "false"); - OMC_ASSERT(proc.returncode != 0, "expected a non-zero exit code"); + STASIS_ASSERT(proc.returncode != 0, "expected a non-zero exit code"); } void test_shell() { @@ -95,28 +95,28 @@ void test_shell() { struct Process *proc = &procs[i]; switch (i) { case 0: - OMC_ASSERT(proc->returncode == 0, "echo should not fail"); + STASIS_ASSERT(proc->returncode == 0, "echo should not fail"); break; case 1: - OMC_ASSERT(proc->returncode == 0, "echo should not fail"); - OMC_ASSERT(access(proc->f_stdout, F_OK) == 0, "stdout.log should exist"); - OMC_ASSERT(access(proc->f_stderr, F_OK) != 0, "stderr.log should not exist"); - OMC_ASSERT(ascii_file_contains(proc->f_stdout, "test_stdout\n"), "output file did not contain test message"); + STASIS_ASSERT(proc->returncode == 0, "echo should not fail"); + STASIS_ASSERT(access(proc->f_stdout, F_OK) == 0, "stdout.log should exist"); + STASIS_ASSERT(access(proc->f_stderr, F_OK) != 0, "stderr.log should not exist"); + STASIS_ASSERT(ascii_file_contains(proc->f_stdout, "test_stdout\n"), "output file did not contain test message"); remove(proc->f_stdout); break; case 2: - OMC_ASSERT(proc->returncode == 0, "echo should not fail"); - OMC_ASSERT(access(proc->f_stdout, F_OK) != 0, "stdout.log should not exist"); - OMC_ASSERT(access(proc->f_stderr, F_OK) == 0, "stderr.log should exist"); - OMC_ASSERT(ascii_file_contains(proc->f_stderr, "test_stderr\n"), "output file did not contain test message"); + STASIS_ASSERT(proc->returncode == 0, "echo should not fail"); + STASIS_ASSERT(access(proc->f_stdout, F_OK) != 0, "stdout.log should not exist"); + STASIS_ASSERT(access(proc->f_stderr, F_OK) == 0, "stderr.log should exist"); + STASIS_ASSERT(ascii_file_contains(proc->f_stderr, "test_stderr\n"), "output file did not contain test message"); remove(proc->f_stderr); break; case 3: - OMC_ASSERT(proc->returncode == 0, "echo should not fail"); - OMC_ASSERT(access("stdouterr.log", F_OK) == 0, "stdouterr.log should exist"); - OMC_ASSERT(access("stderr.log", F_OK) != 0, "stdout.log should not exist"); - OMC_ASSERT(access("stderr.log", F_OK) != 0, "stderr.log should not exist"); - OMC_ASSERT(ascii_file_contains(proc->f_stdout, "test_stdout\ntest_stderr\n"), "output file did not contain test message"); + STASIS_ASSERT(proc->returncode == 0, "echo should not fail"); + STASIS_ASSERT(access("stdouterr.log", F_OK) == 0, "stdouterr.log should exist"); + STASIS_ASSERT(access("stderr.log", F_OK) != 0, "stdout.log should not exist"); + STASIS_ASSERT(access("stderr.log", F_OK) != 0, "stderr.log should not exist"); + STASIS_ASSERT(ascii_file_contains(proc->f_stdout, "test_stdout\ntest_stderr\n"), "output file did not contain test message"); remove(proc->f_stdout); remove(proc->f_stderr); break; @@ -127,8 +127,8 @@ void test_shell() { } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_shell_output_null_args, test_shell_output_non_zero_exit, test_shell_output, @@ -140,6 +140,6 @@ int main(int argc, char *argv[]) { test_shell_exit, test_shell, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } diff --git a/tests/test_template.c b/tests/test_template.c index fda860a..05d8242 100644 --- a/tests/test_template.c +++ b/tests/test_template.c @@ -39,9 +39,9 @@ void test_tpl_workflow() { tpl_reset(); tpl_register("hello_message", &data); - OMC_ASSERT(strcmp(tpl_render("I said, \"{{ hello_message }}\""), "I said, \"Hello world!\"") == 0, "stored value in key is incorrect"); + STASIS_ASSERT(strcmp(tpl_render("I said, \"{{ hello_message }}\""), "I said, \"Hello world!\"") == 0, "stored value in key is incorrect"); setenv("HELLO", "Hello environment!", 1); - OMC_ASSERT(strcmp(tpl_render("{{ env:HELLO }}"), "Hello environment!") == 0, "environment variable content mismatch"); + STASIS_ASSERT(strcmp(tpl_render("{{ env:HELLO }}"), "Hello environment!") == 0, "environment variable content mismatch"); unsetenv("HELLO"); guard_free(data); } @@ -52,8 +52,8 @@ void test_tpl_register() { unsigned used_before_register = tpl_pool_used; tpl_register("hello_message", &data); - OMC_ASSERT(tpl_pool_used == (used_before_register + 1), "tpl_register did not increment allocation counter"); - OMC_ASSERT(tpl_pool[used_before_register] != NULL, "register did not allocate a tpl_item record in the pool"); + STASIS_ASSERT(tpl_pool_used == (used_before_register + 1), "tpl_register did not increment allocation counter"); + STASIS_ASSERT(tpl_pool[used_before_register] != NULL, "register did not allocate a tpl_item record in the pool"); free(data); } @@ -69,32 +69,32 @@ void test_tpl_register_func() { tpl_register_func("sub", &tasks[1]); tpl_register_func("mul", &tasks[2]); tpl_register_func("div", &tasks[3]); - OMC_ASSERT(tpl_pool_func_used == sizeof(tasks) / sizeof(*tasks), "unexpected function pool used"); + STASIS_ASSERT(tpl_pool_func_used == sizeof(tasks) / sizeof(*tasks), "unexpected function pool used"); char *result = NULL; result = tpl_render("{{ func:add(0,3) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); result = tpl_render("{{ func:add(1,2) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); result = tpl_render("{{ func:sub(6,3) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); result = tpl_render("{{ func:sub(4,1) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); result = tpl_render("{{ func:mul(1, 3) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); result = tpl_render("{{ func:div(6,2) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); result = tpl_render("{{ func:div(3,1) }}"); - OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + STASIS_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_tpl_workflow, test_tpl_register_func, test_tpl_register, }; - OMC_TEST_RUN(tests); - OMC_TEST_END_MAIN(); + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/test_utils.c b/tests/test_utils.c index a2fa8c6..4b19604 100644 --- a/tests/test_utils.c +++ b/tests/test_utils.c @@ -18,8 +18,8 @@ void test_listdir() { } struct StrList *listing; listing = listdir(dirs[0]); - OMC_ASSERT(listing != NULL, "listdir failed"); - OMC_ASSERT(listing && (strlist_count(listing) == (sizeof(dirs) / sizeof(*dirs)) - 1), "should have 3"); + STASIS_ASSERT(listing != NULL, "listdir failed"); + STASIS_ASSERT(listing && (strlist_count(listing) == (sizeof(dirs) / sizeof(*dirs)) - 1), "should have 3"); guard_strlist_free(&listing); } @@ -45,7 +45,7 @@ void test_redact_sensitive() { char *input = strdup(data[i]); char output[100] = {0}; redact_sensitive(to_redact, sizeof(to_redact) / sizeof(*to_redact), input, output, sizeof(output) - 1); - OMC_ASSERT(strcmp(output, expected[i]) == 0, "incorrect redaction"); + STASIS_ASSERT(strcmp(output, expected[i]) == 0, "incorrect redaction"); } } @@ -64,13 +64,13 @@ void test_fix_tox_conf() { if (fp) { fprintf(fp, "%s", data); fclose(fp); - OMC_ASSERT(fix_tox_conf(filename, &result) == 0, "fix_tox_conf failed"); + STASIS_ASSERT(fix_tox_conf(filename, &result) == 0, "fix_tox_conf failed"); } else { - OMC_ASSERT(false, "writing mock tox.ini failed"); + STASIS_ASSERT(false, "writing mock tox.ini failed"); } char **lines = file_readlines(result, 0, 0, NULL); - OMC_ASSERT(strstr_array(lines, expected) != NULL, "{posargs} not found in result"); + STASIS_ASSERT(strstr_array(lines, expected) != NULL, "{posargs} not found in result"); remove(result); guard_free(result); @@ -91,30 +91,30 @@ void test_xml_pretty_print_in_place() { if (fp) { fprintf(fp, "%s", data); fclose(fp); - OMC_ASSERT(xml_pretty_print_in_place( + STASIS_ASSERT(xml_pretty_print_in_place( filename, - OMC_XML_PRETTY_PRINT_PROG, - OMC_XML_PRETTY_PRINT_ARGS) == 0, + STASIS_XML_PRETTY_PRINT_PROG, + STASIS_XML_PRETTY_PRINT_ARGS) == 0, "xml pretty print failed (xmllint not installed?)"); } else { - OMC_ASSERT(false, "failed to create input file"); + STASIS_ASSERT(false, "failed to create input file"); } fp = fopen(filename, "r"); char buf[BUFSIZ] = {0}; if (fread(buf, sizeof(*buf), sizeof(buf) - 1, fp) < 1) { - OMC_ASSERT(false, "failed to consume formatted xml file contents"); + STASIS_ASSERT(false, "failed to consume formatted xml file contents"); } - OMC_ASSERT(strcmp(expected, buf) == 0, "xml file was not reformatted"); + STASIS_ASSERT(strcmp(expected, buf) == 0, "xml file was not reformatted"); } void test_path_store() { char *dest = NULL; chdir(cwd_workspace); int result = path_store(&dest, PATH_MAX, cwd_workspace, "test_path_store"); - OMC_ASSERT(result == 0, "path_store encountered an error"); - OMC_ASSERT(dest != NULL, "dest should not be NULL"); - OMC_ASSERT(dest && (access(dest, F_OK) == 0), "destination path was not created"); + STASIS_ASSERT(result == 0, "path_store encountered an error"); + STASIS_ASSERT(dest != NULL, "dest should not be NULL"); + STASIS_ASSERT(dest && (access(dest, F_OK) == 0), "destination path was not created"); rmtree(dest); } @@ -122,13 +122,13 @@ void test_isempty_dir() { const char *dname = "test_isempty_dir"; rmtree(dname); mkdir(dname, 0755); - OMC_ASSERT(isempty_dir(dname) > 0, "empty directory went undetected"); + STASIS_ASSERT(isempty_dir(dname) > 0, "empty directory went undetected"); char path[PATH_MAX]; sprintf(path, "%s/file.txt", dname); touch(path); - OMC_ASSERT(isempty_dir(dname) == 0, "populated directory went undetected"); + STASIS_ASSERT(isempty_dir(dname) == 0, "populated directory went undetected"); } void test_xmkstemp() { @@ -137,7 +137,7 @@ void test_xmkstemp() { const char *data = "Hello, world!\n"; tempfile = xmkstemp(&tempfp, "w"); - OMC_ASSERT(tempfile != NULL, "failed to create temporary file"); + STASIS_ASSERT(tempfile != NULL, "failed to create temporary file"); fprintf(tempfp, "%s", data); fclose(tempfp); @@ -146,7 +146,7 @@ void test_xmkstemp() { fgets(buf, sizeof(buf) - 1, tempfp); fclose(tempfp); - OMC_ASSERT(strcmp(buf, data) == 0, "data written to temp file is incorrect"); + STASIS_ASSERT(strcmp(buf, data) == 0, "data written to temp file is incorrect"); remove(tempfile); free(tempfile); } @@ -154,16 +154,16 @@ void test_xmkstemp() { void test_msg() { int flags_indent[] = { 0, - OMC_MSG_L1, - OMC_MSG_L2, - OMC_MSG_L3, + STASIS_MSG_L1, + STASIS_MSG_L2, + STASIS_MSG_L3, }; int flags_info[] = { - //OMC_MSG_NOP, - OMC_MSG_SUCCESS, - OMC_MSG_WARN, - OMC_MSG_ERROR, - //OMC_MSG_RESTRICT, + //STASIS_MSG_NOP, + STASIS_MSG_SUCCESS, + STASIS_MSG_WARN, + STASIS_MSG_ERROR, + //STASIS_MSG_RESTRICT, }; for (size_t i = 0; i < sizeof(flags_indent) / sizeof(*flags_indent); i++) { for (size_t x = 0; x < sizeof(flags_info) / sizeof(*flags_info); x++) { @@ -193,13 +193,13 @@ void test_git_clone_and_describe() { system(init_cmd); // clone the bare repo - OMC_ASSERT(git_clone(&proc, repo_git, repo, NULL) == 0, + STASIS_ASSERT(git_clone(&proc, repo_git, repo, NULL) == 0, "a local clone of bare repository should have succeeded"); chdir(repo); // interact with it touch("README.md"); - system("git config user.name omc"); + system("git config user.name stasis"); system("git config user.email null@null.null"); system("git add README.md"); system("git commit --no-gpg-sign -m Test"); @@ -208,33 +208,33 @@ void test_git_clone_and_describe() { // test git_describe is functional char *taginfo_none = git_describe("."); - OMC_ASSERT(taginfo_none != NULL, "should be a git hash, not NULL"); + STASIS_ASSERT(taginfo_none != NULL, "should be a git hash, not NULL"); system("git tag -a 1.0.0 -m Mock"); system("git push --tags origin"); char *taginfo = git_describe("."); - OMC_ASSERT(taginfo != NULL, "should be 1.0.0, not NULL"); - OMC_ASSERT(strcmp(taginfo, "1.0.0") == 0, "just-created tag was not described correctly"); + STASIS_ASSERT(taginfo != NULL, "should be 1.0.0, not NULL"); + STASIS_ASSERT(strcmp(taginfo, "1.0.0") == 0, "just-created tag was not described correctly"); chdir(".."); char *taginfo_outer = git_describe(repo); - OMC_ASSERT(taginfo_outer != NULL, "should be 1.0.0, not NULL"); - OMC_ASSERT(strcmp(taginfo_outer, "1.0.0") == 0, "just-created tag was not described correctly (out-of-dir invocation)"); + STASIS_ASSERT(taginfo_outer != NULL, "should be 1.0.0, not NULL"); + STASIS_ASSERT(strcmp(taginfo_outer, "1.0.0") == 0, "just-created tag was not described correctly (out-of-dir invocation)"); char *taginfo_bad = git_describe("abc1234_not_here_or_there"); - OMC_ASSERT(taginfo_bad == NULL, "a repository that shouldn't exist... exists and has a tag."); + STASIS_ASSERT(taginfo_bad == NULL, "a repository that shouldn't exist... exists and has a tag."); chdir(cwd); } void test_touch() { - OMC_ASSERT(touch("touchedfile.txt") == 0, "touch failed"); - OMC_ASSERT(access("touchedfile.txt", F_OK) == 0, "touched file does not exist"); + STASIS_ASSERT(touch("touchedfile.txt") == 0, "touch failed"); + STASIS_ASSERT(access("touchedfile.txt", F_OK) == 0, "touched file does not exist"); remove("touchedfile.txt"); } void test_find_program() { - OMC_ASSERT(find_program("willnotexist123") == NULL, "did not return NULL"); - OMC_ASSERT(find_program("find") != NULL, "program not available (OS dependent)"); + STASIS_ASSERT(find_program("willnotexist123") == NULL, "did not return NULL"); + STASIS_ASSERT(find_program("find") != NULL, "program not available (OS dependent)"); } static int file_readlines_callback_modify(size_t size, char **line) { @@ -276,16 +276,16 @@ void test_file_readlines() { result = file_readlines(filename, 0, 0, NULL); int i; for (i = 0; result[i] != NULL; i++); - OMC_ASSERT(num_chars(data, '\n') == i, "incorrect number of lines in data"); - OMC_ASSERT(strcmp(result[3], "see?\n") == 0, "last line in data is incorrect'"); + STASIS_ASSERT(num_chars(data, '\n') == i, "incorrect number of lines in data"); + STASIS_ASSERT(strcmp(result[3], "see?\n") == 0, "last line in data is incorrect'"); GENERIC_ARRAY_FREE(result); result = file_readlines(filename, 0, 0, file_readlines_callback_modify); - OMC_ASSERT(strcmp(result[3], "xxx?\n") == 0, "last line should be: 'xxx?\\n'"); + STASIS_ASSERT(strcmp(result[3], "xxx?\n") == 0, "last line should be: 'xxx?\\n'"); GENERIC_ARRAY_FREE(result); result = file_readlines(filename, 0, 0, file_readlines_callback_get_specific_line); - OMC_ASSERT(strcmp(result[0], "see?\n") == 0, "the first record of the result is not the last line of the file 'see?\\n'"); + STASIS_ASSERT(strcmp(result[0], "see?\n") == 0, "the first record of the result is not the last line of the file 'see?\\n'"); GENERIC_ARRAY_FREE(result); remove(filename); } @@ -302,7 +302,7 @@ void test_path_dirname() { strcpy(tmp, input); char *result = path_dirname(tmp); - OMC_ASSERT(strcmp(expected, result) == 0, NULL); + STASIS_ASSERT(strcmp(expected, result) == 0, NULL); } } @@ -315,7 +315,7 @@ void test_path_basename() { const char *input = data[i]; const char *expected = data[i + 1]; char *result = path_basename(input); - OMC_ASSERT(strcmp(expected, result) == 0, NULL); + STASIS_ASSERT(strcmp(expected, result) == 0, NULL); } } @@ -338,8 +338,8 @@ void test_expandpath() { strcat(path, DIR_SEP); char *expanded = expandpath(path); - OMC_ASSERT(startswith(expanded, home) > 0, expanded); - OMC_ASSERT(endswith(expanded, DIR_SEP) > 0, "the input string ends with a directory separator, the result did not"); + STASIS_ASSERT(startswith(expanded, home) > 0, expanded); + STASIS_ASSERT(endswith(expanded, DIR_SEP) > 0, "the input string ends with a directory separator, the result did not"); free(expanded); } @@ -361,12 +361,12 @@ void test_rmtree() { sprintf(mockfile, "%s/%zu.txt", path, i); if (mkdir(path, 0755)) { perror(path); - OMC_ASSERT(false, NULL); + STASIS_ASSERT(false, NULL); } touch(mockfile); } - OMC_ASSERT(rmtree(root) == 0, "rmtree should have been able to remove the directory"); - OMC_ASSERT(access(root, F_OK) < 0, "the directory is still present"); + STASIS_ASSERT(rmtree(root) == 0, "rmtree should have been able to remove the directory"); + STASIS_ASSERT(access(root, F_OK) < 0, "the directory is still present"); } void test_dirstack() { @@ -384,18 +384,18 @@ void test_dirstack() { pushd(data[i]); getcwd(cwd, PATH_MAX); } - OMC_ASSERT(dirstack_len == sizeof(data) / sizeof(*data), NULL); - OMC_ASSERT(strcmp(dirstack[0], cwd_workspace) == 0, NULL); + STASIS_ASSERT(dirstack_len == sizeof(data) / sizeof(*data), NULL); + STASIS_ASSERT(strcmp(dirstack[0], cwd_workspace) == 0, NULL); for (int i = 1; i < dirstack_len; i++) { - OMC_ASSERT(endswith(dirstack[i], data[i - 1]), NULL); + STASIS_ASSERT(endswith(dirstack[i], data[i - 1]), NULL); } for (size_t i = 0, x = dirstack_len - 1; x != 0 && i < sizeof(data) / sizeof(*data); i++, x--) { char *expected = strdup(dirstack[x]); popd(); getcwd(cwd, PATH_MAX); - OMC_ASSERT(strcmp(cwd, expected) == 0, NULL); + STASIS_ASSERT(strcmp(cwd, expected) == 0, NULL); free(expected); } } @@ -405,16 +405,16 @@ void test_pushd_popd() { chdir(cwd_workspace); rmtree(dname); - OMC_ASSERT(mkdir(dname, 0755) == 0, "directory should not exist yet"); - OMC_ASSERT(pushd(dname) == 0, "failed to enter directory"); + STASIS_ASSERT(mkdir(dname, 0755) == 0, "directory should not exist yet"); + STASIS_ASSERT(pushd(dname) == 0, "failed to enter directory"); char *cwd = getcwd(NULL, PATH_MAX); // we should be inside the test directory, not the starting directory - OMC_ASSERT(strcmp(cwd_workspace, cwd) != 0, ""); - OMC_ASSERT(popd() == 0, "return from directory failed"); + STASIS_ASSERT(strcmp(cwd_workspace, cwd) != 0, ""); + STASIS_ASSERT(popd() == 0, "return from directory failed"); char *cwd_after_popd = getcwd(NULL, PATH_MAX); - OMC_ASSERT(strcmp(cwd_workspace, cwd_after_popd) == 0, "should be the path where we started"); + STASIS_ASSERT(strcmp(cwd_workspace, cwd_after_popd) == 0, "should be the path where we started"); } void test_pushd_popd_suggested_workflow() { @@ -426,23 +426,23 @@ void test_pushd_popd_suggested_workflow() { if (!mkdir(dname, 0755)) { if (!pushd(dname)) { char *cwd = getcwd(NULL, PATH_MAX); - OMC_ASSERT(strcmp(cwd_workspace, cwd) != 0, NULL); + STASIS_ASSERT(strcmp(cwd_workspace, cwd) != 0, NULL); // return from dir popd(); free(cwd); } // cwd should be our starting directory char *cwd = getcwd(NULL, PATH_MAX); - OMC_ASSERT(strcmp(cwd_workspace, cwd) == 0, NULL); + STASIS_ASSERT(strcmp(cwd_workspace, cwd) == 0, NULL); } else { - OMC_ASSERT(false, "mkdir function failed"); + STASIS_ASSERT(false, "mkdir function failed"); } } int main(int argc, char *argv[]) { - OMC_TEST_BEGIN_MAIN(); - OMC_TEST_FUNC *tests[] = { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { test_listdir, test_redact_sensitive, test_fix_tox_conf, @@ -469,11 +469,11 @@ int main(int argc, char *argv[]) { chdir(ws); getcwd(cwd_workspace, sizeof(cwd_workspace) - 1); - OMC_TEST_RUN(tests); + STASIS_TEST_RUN(tests); chdir(cwd_start); if (rmtree(cwd_workspace)) { perror(cwd_workspace); } - OMC_TEST_END_MAIN(); + STASIS_TEST_END_MAIN(); } \ No newline at end of file diff --git a/tests/testing.h b/tests/testing.h index 35bfbd2..15bd208 100644 --- a/tests/testing.h +++ b/tests/testing.h @@ -1,16 +1,16 @@ -#ifndef OMC_TESTING_H -#define OMC_TESTING_H -#include "omc.h" -#define OMC_TEST_RUN_MAX 10000 -#define OMC_TEST_SUITE_FATAL 1 -#define OMC_TEST_SUITE_SKIP 127 +#ifndef STASIS_TESTING_H +#define STASIS_TESTING_H +#include "core.h" +#define STASIS_TEST_RUN_MAX 10000 +#define STASIS_TEST_SUITE_FATAL 1 +#define STASIS_TEST_SUITE_SKIP 127 #ifndef __FILE_NAME__ #define __FILE_NAME__ __FILE__ #endif -typedef void(OMC_TEST_FUNC)(); -struct omc_test_result_t { +typedef void(STASIS_TEST_FUNC)(); +struct stasis_test_result_t { const char *filename; const char *funcname; int lineno; @@ -18,36 +18,36 @@ struct omc_test_result_t { const char *msg_reason; const int status; const int skip; -} omc_test_results[OMC_TEST_RUN_MAX]; -size_t omc_test_results_i = 0; +} stasis_test_results[STASIS_TEST_RUN_MAX]; +size_t stasis_test_results_i = 0; -void omc_testing_record_result(struct omc_test_result_t result); +void stasis_testing_record_result(struct stasis_test_result_t result); -void omc_testing_record_result(struct omc_test_result_t result) { - memcpy(&omc_test_results[omc_test_results_i], &result, sizeof(result)); - omc_test_results_i++; +void stasis_testing_record_result(struct stasis_test_result_t result) { + memcpy(&stasis_test_results[stasis_test_results_i], &result, sizeof(result)); + stasis_test_results_i++; } -int omc_testing_has_failed() { - for (size_t i = 0; i < omc_test_results_i; i++) { - if (omc_test_results[i].status == false) { +int stasis_testing_has_failed() { + for (size_t i = 0; i < stasis_test_results_i; i++) { + if (stasis_test_results[i].status == false) { return 1; } } return 0; } -void omc_testing_record_result_summary() { +void stasis_testing_record_result_summary() { size_t failed = 0; size_t skipped = 0; size_t passed = 0; int do_message; static char status_msg[255] = {0}; - for (size_t i = 0; i < omc_test_results_i; i++) { - if (omc_test_results[i].status && omc_test_results[i].skip) { + for (size_t i = 0; i < stasis_test_results_i; i++) { + if (stasis_test_results[i].status && stasis_test_results[i].skip) { strcpy(status_msg, "SKIP"); do_message = 1; skipped++; - } else if (!omc_test_results[i].status) { + } else if (!stasis_test_results[i].status) { strcpy(status_msg, "FAIL"); do_message = 1; failed++; @@ -58,19 +58,19 @@ void omc_testing_record_result_summary() { } fprintf(stdout, "[%s] %s:%d :: %s() => %s", status_msg, - omc_test_results[i].filename, - omc_test_results[i].lineno, - omc_test_results[i].funcname, - omc_test_results[i].msg_assertion); + stasis_test_results[i].filename, + stasis_test_results[i].lineno, + stasis_test_results[i].funcname, + stasis_test_results[i].msg_assertion); if (do_message) { - fprintf(stdout, "\n \\_ %s", omc_test_results[i].msg_reason); + fprintf(stdout, "\n \\_ %s", stasis_test_results[i].msg_reason); } fprintf(stdout, "\n"); } - fprintf(stdout, "\n[UNIT] %zu tests passed, %zu tests failed, %zu tests skipped out of %zu\n", passed, failed, skipped, omc_test_results_i); + fprintf(stdout, "\n[UNIT] %zu tests passed, %zu tests failed, %zu tests skipped out of %zu\n", passed, failed, skipped, stasis_test_results_i); } -char *omc_testing_read_ascii(const char *filename) { +char *stasis_testing_read_ascii(const char *filename) { struct stat st; if (stat(filename, &st)) { perror(filename); @@ -96,7 +96,7 @@ char *omc_testing_read_ascii(const char *filename) { return result; } -int omc_testing_write_ascii(const char *filename, const char *data) { +int stasis_testing_write_ascii(const char *filename, const char *data) { FILE *fp; fp = fopen(filename, "w+"); if (!fp) { @@ -114,18 +114,18 @@ int omc_testing_write_ascii(const char *filename, const char *data) { return 0; } -#define OMC_TEST_BEGIN_MAIN() do { \ +#define STASIS_TEST_BEGIN_MAIN() do { \ setenv("PYTHONUNBUFFERED", "1", 1); \ fflush(stdout); \ fflush(stderr); \ setvbuf(stdout, NULL, _IONBF, 0); \ setvbuf(stderr, NULL, _IONBF, 0); \ - atexit(omc_testing_record_result_summary); \ + atexit(stasis_testing_record_result_summary); \ } while (0) -#define OMC_TEST_END_MAIN() do { return omc_testing_has_failed(); } while (0) +#define STASIS_TEST_END_MAIN() do { return stasis_testing_has_failed(); } while (0) -#define OMC_ASSERT(COND, REASON) do { \ - omc_testing_record_result((struct omc_test_result_t) { \ +#define STASIS_ASSERT(COND, REASON) do { \ + stasis_testing_record_result((struct stasis_test_result_t) { \ .filename = __FILE_NAME__, \ .funcname = __FUNCTION__, \ .lineno = __LINE__, \ @@ -134,8 +134,8 @@ int omc_testing_write_ascii(const char *filename, const char *data) { .msg_reason = REASON } ); \ } while (0) -#define OMC_ASSERT_FATAL(COND, REASON) do { \ - omc_testing_record_result((struct omc_test_result_t) { \ +#define STASIS_ASSERT_FATAL(COND, REASON) do { \ + stasis_testing_record_result((struct stasis_test_result_t) { \ .filename = __FILE_NAME__, \ .funcname = __FUNCTION__, \ .lineno = __LINE__, \ @@ -143,13 +143,13 @@ int omc_testing_write_ascii(const char *filename, const char *data) { .msg_assertion = "ASSERT FATAL (" #COND ")", \ .msg_reason = REASON } \ ); \ - if (omc_test_results[omc_test_results_i ? omc_test_results_i - 1 : omc_test_results_i].status == false) {\ - exit(OMC_TEST_SUITE_FATAL); \ + if (stasis_test_results[stasis_test_results_i ? stasis_test_results_i - 1 : stasis_test_results_i].status == false) {\ + exit(STASIS_TEST_SUITE_FATAL); \ } \ } while (0) -#define OMC_SKIP_IF(COND, REASON) do { \ - omc_testing_record_result((struct omc_test_result_t) { \ +#define STASIS_SKIP_IF(COND, REASON) do { \ + stasis_testing_record_result((struct stasis_test_result_t) { \ .filename = __FILE_NAME__, \ .funcname = __FUNCTION__, \ .lineno = __LINE__, \ @@ -158,12 +158,12 @@ int omc_testing_write_ascii(const char *filename, const char *data) { .msg_assertion = "SKIP (" #COND ")", \ .msg_reason = REASON } \ ); \ - if (omc_test_results[omc_test_results_i ? omc_test_results_i - 1 : omc_test_results_i].skip == true) {\ + if (stasis_test_results[stasis_test_results_i ? stasis_test_results_i - 1 : stasis_test_results_i].skip == true) {\ return; \ } \ } while (0) -#define OMC_TEST_RUN(X) do { \ +#define STASIS_TEST_RUN(X) do { \ for (size_t i = 0; i < sizeof(X) / sizeof(*X); i++) { \ if (X[i]) { \ X[i](); \ @@ -171,4 +171,4 @@ int omc_testing_write_ascii(const char *filename, const char *data) { } \ } while (0) -#endif //OMC_TESTING_H +#endif //STASIS_TESTING_H -- cgit