diff options
author | Joseph Hunkeler <jhunkeler@users.noreply.github.com> | 2024-08-20 10:45:09 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-20 10:45:09 -0400 |
commit | 0eda05963f3c70c3969ddd2aa72926b871ef4b07 (patch) | |
tree | f38b6fd60a36b82e1247ff60ef28e694cd9517ff | |
parent | a7568dc03c1c6851ff6c690e8e35ade9a3199c4a (diff) | |
download | stasis-0eda05963f3c70c3969ddd2aa72926b871ef4b07.tar.gz |
Pypi existence check (#30)
* Add python_package_exists() function
* Poll pypi.org or compatible index to see if a package exists
* Returns non-zero on success
* Implements python_package_exists() in delivery_defer_packages()
* Implements python_package_exists() in delivery_defer_packages()
* Bugfix: Avoid incorrect package selection
* With large package lists that contain multiple packages starting with the same strstr() would pick the first match
* This adds a temporary name variable that strcmp() can check against.
* Message correction:
* Change "release" to "testing" in testing environment failure message
* Amend message to fit the flow of the output
* Disable outdated conda notifications
* The latest version isn't always the greatest. Don't give the end-user any ideas. Just use whatever the installer provides... quietly
* Rename python_package_exists to pip_index_provides
* Document the function prototype
* Add missing comments in micromamba structure
* Ensure the temporary output file does not linger
-rw-r--r-- | include/conda.h | 15 | ||||
-rw-r--r-- | src/conda.c | 80 | ||||
-rw-r--r-- | src/delivery.c | 15 | ||||
-rw-r--r-- | src/stasis_main.c | 2 |
4 files changed, 101 insertions, 11 deletions
diff --git a/include/conda.h b/include/conda.h index d439371..c546672 100644 --- a/include/conda.h +++ b/include/conda.h @@ -7,10 +7,11 @@ #include "core.h" #define CONDA_INSTALL_PREFIX "conda" +#define PYPI_INDEX_DEFAULT "https://pypi.org/simple" struct MicromambaInfo { - char *micromamba_prefix; - char *conda_prefix; + char *micromamba_prefix; //!< Path to write micromamba binary + char *conda_prefix; //!< Path to install conda base tree }; /** @@ -181,4 +182,14 @@ int conda_env_export(char *name, char *output_dir, char *output_filename); * @return exit code from "conda" */ int conda_index(const char *path); + +/** + * Determine whether a simple index contains a package + * @param index_url a file system path or url pointing to a simple index + * @param name package name (required) + * @param version package version (may be NULL) + * @return not found = 0, found = 1, error = -1 + */ +int pip_index_provides(const char *index_url, const char *name, const char *version); + #endif //STASIS_CONDA_H diff --git a/src/conda.c b/src/conda.c index 6ed96c7..ff55f14 100644 --- a/src/conda.c +++ b/src/conda.c @@ -79,6 +79,73 @@ int pip_exec(const char *args) { return system(command); } +int pip_index_provides(const char *index_url, const char *name, const char *version) { + char cmd[PATH_MAX] = {0}; + char name_local[255]; + char version_local[255] = {0}; + char spec[255] = {0}; + + if (isempty((char *) name) < 0) { + // no package name means nothing to do. + return -1; + } + + // Fix up the package name + strncpy(name_local, name, sizeof(name_local) - 1); + tolower_s(name_local); + lstrip(name_local); + strip(name_local); + + if (version) { + // Fix up the package version + strncpy(version_local, version, sizeof(version_local) - 1); + tolower_s(version_local); + lstrip(version_local); + strip(version_local); + sprintf(spec, "==%s", version); + } + + char logfile[] = "/tmp/STASIS-package_exists.XXXXXX"; + int logfd = mkstemp(logfile); + if (logfd < 0) { + perror(logfile); + remove(logfile); // fail harmlessly if not present + return -1; + } + + + int status = 0; + struct Process proc; + memset(&proc, 0, sizeof(proc)); + proc.redirect_stderr = 1; + strcpy(proc.f_stdout, logfile); + + // Do an installation in dry-run mode to see if the package exists in the given index. + snprintf(cmd, sizeof(cmd) - 1, "python -m pip install --dry-run --no-deps --index-url=%s %s%s", index_url, name_local, spec); + status = shell(&proc, cmd); + + // Print errors only when shell() itself throws one + // If some day we want to see the errors thrown by pip too, use this condition instead: (status != 0) + if (status < 0) { + FILE *fp = fdopen(logfd, "r"); + if (!fp) { + remove(logfile); + return -1; + } else { + char line[BUFSIZ] = {0}; + fflush(stdout); + fflush(stderr); + while (fgets(line, sizeof(line) - 1, fp) != NULL) { + fprintf(stderr, "%s", line); + } + fflush(stderr); + fclose(fp); + } + } + remove(logfile); + return proc.returncode == 0; +} + int conda_exec(const char *args) { char command[PATH_MAX]; const char *mamba_commands[] = { @@ -275,12 +342,13 @@ int conda_setup_headless() { } // Configure conda for headless CI - conda_exec("config --system --set auto_update_conda false"); // never update conda automatically - conda_exec("config --system --set always_yes true"); // never prompt for input - conda_exec("config --system --set safety_checks disabled"); // speedup - conda_exec("config --system --set rollback_enabled false"); // speedup - conda_exec("config --system --set report_errors false"); // disable data sharing - conda_exec("config --system --set solver libmamba"); // use a real solver + conda_exec("config --system --set auto_update_conda false"); // never update conda automatically + conda_exec("config --system --set notify_outdated_conda false"); // never notify about outdated conda version + conda_exec("config --system --set always_yes true"); // never prompt for input + conda_exec("config --system --set safety_checks disabled"); // speedup + conda_exec("config --system --set rollback_enabled false"); // speedup + conda_exec("config --system --set report_errors false"); // disable data sharing + conda_exec("config --system --set solver libmamba"); // use a real solver char cmd[PATH_MAX]; size_t total = 0; diff --git a/src/delivery.c b/src/delivery.c index e69ce2f..524dd0a 100644 --- a/src/delivery.c +++ b/src/delivery.c @@ -1466,8 +1466,14 @@ void delivery_defer_packages(struct Delivery *ctx, int type) { struct Test *test = &ctx->tests[x]; version = NULL; + char nametmp[1024] = {0}; + if (spec_end != NULL && spec_begin != NULL) { + strncpy(nametmp, name, spec_begin - name); + } else { + strcpy(nametmp, name); + } // Is the [test:NAME] in the package name? - if (strstr(name, test->name)) { + if (!strcmp(nametmp, test->name)) { // Override test->version when a version is provided by the (pip|conda)_package list item guard_free(test->version); if (spec_begin && spec_end) { @@ -1498,7 +1504,12 @@ void delivery_defer_packages(struct Delivery *ctx, int type) { } } - ignore_pkg = 1; + if (DEFER_PIP == type && pip_index_provides(PYPI_INDEX_DEFAULT, name, version)) { + fprintf(stderr, "(%s present on index %s): ", version, PYPI_INDEX_DEFAULT); + ignore_pkg = 0; + } else { + ignore_pkg = 1; + } break; } } diff --git a/src/stasis_main.c b/src/stasis_main.c index cf07b3a..7ea465c 100644 --- a/src/stasis_main.c +++ b/src/stasis_main.c @@ -482,7 +482,7 @@ int main(int argc, char *argv[]) { 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"); + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "failed to create testing environment\n"); exit(1); } } |