From eeef791aab59c56f6ca45e42a400bff0c6894a23 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Wed, 4 Feb 2026 10:36:38 -0500 Subject: Rename wheel.c and wheel.h to wheelinfo.c and wheelinfo.h * Refactor wheel struct as WheelInfo * Refactor wheel_* functions as wheelinfo_* --- src/lib/core/CMakeLists.txt | 2 +- src/lib/core/include/wheel.h | 36 ----------- src/lib/core/include/wheelinfo.h | 36 +++++++++++ src/lib/core/wheel.c | 126 ------------------------------------ src/lib/core/wheelinfo.c | 126 ++++++++++++++++++++++++++++++++++++ src/lib/delivery/delivery_install.c | 6 +- src/lib/delivery/include/delivery.h | 2 +- tests/test_wheel.c | 91 -------------------------- tests/test_wheelinfo.c | 91 ++++++++++++++++++++++++++ 9 files changed, 258 insertions(+), 258 deletions(-) delete mode 100644 src/lib/core/include/wheel.h create mode 100644 src/lib/core/include/wheelinfo.h delete mode 100644 src/lib/core/wheel.c create mode 100644 src/lib/core/wheelinfo.c delete mode 100644 tests/test_wheel.c create mode 100644 tests/test_wheelinfo.c diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index eb7a908..d264010 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -11,7 +11,7 @@ add_library(stasis_core STATIC download.c recipe.c relocation.c - wheel.c + wheelinfo.c copy.c artifactory.c template.c diff --git a/src/lib/core/include/wheel.h b/src/lib/core/include/wheel.h deleted file mode 100644 index 1a689e9..0000000 --- a/src/lib/core/include/wheel.h +++ /dev/null @@ -1,36 +0,0 @@ -//! @file wheel.h -#ifndef STASIS_WHEEL_H -#define STASIS_WHEEL_H - -#include -#include -#include -#include "str.h" -#define WHEEL_MATCH_EXACT 0 ///< Match when all patterns are present -#define WHEEL_MATCH_ANY 1 ///< Match when any patterns are present - -struct Wheel { - char *distribution; ///< Package name - char *version; ///< Package version - char *build_tag; ///< Package build tag (optional) - char *python_tag; ///< Package Python tag (pyXY) - char *abi_tag; ///< Package ABI tag (cpXY, abiX, none) - char *platform_tag; ///< Package platform tag (linux_x86_64, any) - char *path_name; ///< Path to package on-disk - char *file_name; ///< Name of package on-disk -}; - -/** - * Extract metadata from a Python Wheel file name - * - * @param basepath directory containing a wheel file - * @param name of wheel file - * @param to_match a NULL terminated array of patterns (i.e. platform, arch, version, etc) - * @param match_mode WHEEL_MATCH_EXACT - * @param match_mode WHEEL_MATCH ANY - * @return pointer to populated Wheel on success - * @return NULL on error - */ -struct Wheel *get_wheel_info(const char *basepath, const char *name, char *to_match[], unsigned match_mode); -void wheel_free(struct Wheel **wheel); -#endif //STASIS_WHEEL_H diff --git a/src/lib/core/include/wheelinfo.h b/src/lib/core/include/wheelinfo.h new file mode 100644 index 0000000..8009e91 --- /dev/null +++ b/src/lib/core/include/wheelinfo.h @@ -0,0 +1,36 @@ +//! @file wheel.h +#ifndef STASIS_WHEEL_H +#define STASIS_WHEEL_H + +#include +#include +#include +#include "str.h" +#define WHEEL_MATCH_EXACT 0 ///< Match when all patterns are present +#define WHEEL_MATCH_ANY 1 ///< Match when any patterns are present + +struct WheelInfo { + char *distribution; ///< Package name + char *version; ///< Package version + char *build_tag; ///< Package build tag (optional) + char *python_tag; ///< Package Python tag (pyXY) + char *abi_tag; ///< Package ABI tag (cpXY, abiX, none) + char *platform_tag; ///< Package platform tag (linux_x86_64, any) + char *path_name; ///< Path to package on-disk + char *file_name; ///< Name of package on-disk +}; + +/** + * Extract metadata from a Python Wheel file name + * + * @param basepath directory containing a wheel file + * @param name of wheel file + * @param to_match a NULL terminated array of patterns (i.e. platform, arch, version, etc) + * @param match_mode WHEEL_MATCH_EXACT + * @param match_mode WHEEL_MATCH ANY + * @return pointer to populated Wheel on success + * @return NULL on error + */ +struct WheelInfo *wheelinfo_get(const char *basepath, const char *name, char *to_match[], unsigned match_mode); +void wheelinfo_free(struct WheelInfo **wheel); +#endif //STASIS_WHEEL_H diff --git a/src/lib/core/wheel.c b/src/lib/core/wheel.c deleted file mode 100644 index c7e485a..0000000 --- a/src/lib/core/wheel.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "wheel.h" - -struct Wheel *get_wheel_info(const char *basepath, const char *name, char *to_match[], unsigned match_mode) { - struct dirent *rec; - struct Wheel *result = NULL; - char package_path[PATH_MAX]; - char package_name[NAME_MAX]; - - strcpy(package_name, name); - tolower_s(package_name); - sprintf(package_path, "%s/%s", basepath, package_name); - - DIR *dp = opendir(package_path); - if (!dp) { - return NULL; - } - - while ((rec = readdir(dp)) != NULL) { - if (!strcmp(rec->d_name, ".") || !strcmp(rec->d_name, "..")) { - continue; - } - char filename[NAME_MAX]; - strcpy(filename, rec->d_name); - char *ext = strstr(filename, ".whl"); - if (ext) { - *ext = '\0'; - } else { - // not a wheel file. nothing to do - continue; - } - - size_t match = 0; - size_t pattern_count = 0; - for (; to_match[pattern_count] != NULL; pattern_count++) { - if (strstr(filename, to_match[pattern_count])) { - match++; - } - } - - if (!startswith(rec->d_name, name)) { - continue; - } - - if (match_mode == WHEEL_MATCH_EXACT && match != pattern_count) { - continue; - } - - result = calloc(1, sizeof(*result)); - if (!result) { - SYSERROR("Unable to allocate %zu bytes for wheel struct", sizeof(*result)); - closedir(dp); - return NULL; - } - - result->path_name = realpath(package_path, NULL); - if (!result->path_name) { - SYSERROR("Unable to resolve absolute path to %s: %s", filename, strerror(errno)); - wheel_free(&result); - closedir(dp); - return NULL; - } - result->file_name = strdup(rec->d_name); - if (!result->file_name) { - SYSERROR("Unable to allocate bytes for %s: %s", rec->d_name, strerror(errno)); - wheel_free(&result); - closedir(dp); - return NULL; - } - - size_t parts_total; - char **parts = split(filename, "-", 0); - if (!parts) { - // This shouldn't happen unless a wheel file is present in the - // directory with a malformed file name, or we've managed to - // exhaust the system's memory - SYSERROR("%s has no '-' separators! (Delete this file and try again)", filename); - wheel_free(&result); - closedir(dp); - return NULL; - } - - for (parts_total = 0; parts[parts_total] != NULL; parts_total++) {} - if (parts_total == 5) { - // no build tag - result->distribution = strdup(parts[0]); - result->version = strdup(parts[1]); - result->build_tag = NULL; - result->python_tag = strdup(parts[2]); - result->abi_tag = strdup(parts[3]); - result->platform_tag = strdup(parts[4]); - } else if (parts_total == 6) { - // has build tag - result->distribution = strdup(parts[0]); - result->version = strdup(parts[1]); - result->build_tag = strdup(parts[2]); - result->python_tag = strdup(parts[3]); - result->abi_tag = strdup(parts[4]); - result->platform_tag = strdup(parts[5]); - } else { - SYSERROR("Unknown wheel name format: %s. Expected 5 or 6 strings " - "separated by '-', but got %zu instead", filename, parts_total); - guard_array_free(parts); - wheel_free(&result); - closedir(dp); - return NULL; - } - guard_array_free(parts); - break; - } - closedir(dp); - return result; -} - -void wheel_free(struct Wheel **wheel) { - struct Wheel *w = (*wheel); - guard_free(w->path_name); - guard_free(w->file_name); - guard_free(w->distribution); - guard_free(w->version); - guard_free(w->build_tag); - guard_free(w->python_tag); - guard_free(w->abi_tag); - guard_free(w->python_tag); - guard_free(w->platform_tag); - guard_free(w); -} diff --git a/src/lib/core/wheelinfo.c b/src/lib/core/wheelinfo.c new file mode 100644 index 0000000..d0e2484 --- /dev/null +++ b/src/lib/core/wheelinfo.c @@ -0,0 +1,126 @@ +#include "wheelinfo.h" + +struct WheelInfo *wheelinfo_get(const char *basepath, const char *name, char *to_match[], unsigned match_mode) { + struct dirent *rec; + struct WheelInfo *result = NULL; + char package_path[PATH_MAX]; + char package_name[NAME_MAX]; + + strcpy(package_name, name); + tolower_s(package_name); + sprintf(package_path, "%s/%s", basepath, package_name); + + DIR *dp = opendir(package_path); + if (!dp) { + return NULL; + } + + while ((rec = readdir(dp)) != NULL) { + if (!strcmp(rec->d_name, ".") || !strcmp(rec->d_name, "..")) { + continue; + } + char filename[NAME_MAX]; + strcpy(filename, rec->d_name); + char *ext = strstr(filename, ".whl"); + if (ext) { + *ext = '\0'; + } else { + // not a wheel file. nothing to do + continue; + } + + size_t match = 0; + size_t pattern_count = 0; + for (; to_match[pattern_count] != NULL; pattern_count++) { + if (strstr(filename, to_match[pattern_count])) { + match++; + } + } + + if (!startswith(rec->d_name, name)) { + continue; + } + + if (match_mode == WHEEL_MATCH_EXACT && match != pattern_count) { + continue; + } + + result = calloc(1, sizeof(*result)); + if (!result) { + SYSERROR("Unable to allocate %zu bytes for wheel struct", sizeof(*result)); + closedir(dp); + return NULL; + } + + result->path_name = realpath(package_path, NULL); + if (!result->path_name) { + SYSERROR("Unable to resolve absolute path to %s: %s", filename, strerror(errno)); + wheelinfo_free(&result); + closedir(dp); + return NULL; + } + result->file_name = strdup(rec->d_name); + if (!result->file_name) { + SYSERROR("Unable to allocate bytes for %s: %s", rec->d_name, strerror(errno)); + wheelinfo_free(&result); + closedir(dp); + return NULL; + } + + size_t parts_total; + char **parts = split(filename, "-", 0); + if (!parts) { + // This shouldn't happen unless a wheel file is present in the + // directory with a malformed file name, or we've managed to + // exhaust the system's memory + SYSERROR("%s has no '-' separators! (Delete this file and try again)", filename); + wheelinfo_free(&result); + closedir(dp); + return NULL; + } + + for (parts_total = 0; parts[parts_total] != NULL; parts_total++) {} + if (parts_total == 5) { + // no build tag + result->distribution = strdup(parts[0]); + result->version = strdup(parts[1]); + result->build_tag = NULL; + result->python_tag = strdup(parts[2]); + result->abi_tag = strdup(parts[3]); + result->platform_tag = strdup(parts[4]); + } else if (parts_total == 6) { + // has build tag + result->distribution = strdup(parts[0]); + result->version = strdup(parts[1]); + result->build_tag = strdup(parts[2]); + result->python_tag = strdup(parts[3]); + result->abi_tag = strdup(parts[4]); + result->platform_tag = strdup(parts[5]); + } else { + SYSERROR("Unknown wheel name format: %s. Expected 5 or 6 strings " + "separated by '-', but got %zu instead", filename, parts_total); + guard_array_free(parts); + wheelinfo_free(&result); + closedir(dp); + return NULL; + } + guard_array_free(parts); + break; + } + closedir(dp); + return result; +} + +void wheelinfo_free(struct WheelInfo **wheel) { + struct WheelInfo *w = (*wheel); + guard_free(w->path_name); + guard_free(w->file_name); + guard_free(w->distribution); + guard_free(w->version); + guard_free(w->build_tag); + guard_free(w->python_tag); + guard_free(w->abi_tag); + guard_free(w->python_tag); + guard_free(w->platform_tag); + guard_free(w); +} diff --git a/src/lib/delivery/delivery_install.c b/src/lib/delivery/delivery_install.c index f1637a3..2d322de 100644 --- a/src/lib/delivery/delivery_install.c +++ b/src/lib/delivery/delivery_install.c @@ -252,7 +252,7 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha } strlist_append_tokenize(tag_data, info->repository_info_tag, "-"); - struct Wheel *whl = NULL; + struct WheelInfo *whl = NULL; char *post_commit = NULL; char *hash = NULL; if (strlist_count(tag_data) > 1) { @@ -264,7 +264,7 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha // equal to the tag; setuptools_scm auto-increments the value, the user can change it manually, // etc. errno = 0; - whl = get_wheel_info(ctx->storage.wheel_artifact_dir, info->name, + whl = wheelinfo_get(ctx->storage.wheel_artifact_dir, info->name, (char *[]) {ctx->meta.python_compact, ctx->system.arch, "none", "any", post_commit, hash, @@ -281,7 +281,7 @@ int delivery_install_packages(struct Delivery *ctx, char *conda_install_dir, cha info->version = strdup(whl->version); } guard_strlist_free(&tag_data); - wheel_free(&whl); + wheelinfo_free(&whl); } char req[255] = {0}; diff --git a/src/lib/delivery/include/delivery.h b/src/lib/delivery/include/delivery.h index cae4b02..ddc819e 100644 --- a/src/lib/delivery/include/delivery.h +++ b/src/lib/delivery/include/delivery.h @@ -18,7 +18,7 @@ #include "ini.h" #include "multiprocessing.h" #include "recipe.h" -#include "wheel.h" +#include "wheelinfo.h" #define DELIVERY_PLATFORM_MAX 4 #define DELIVERY_PLATFORM_MAXLEN 65 diff --git a/tests/test_wheel.c b/tests/test_wheel.c deleted file mode 100644 index 6818b22..0000000 --- a/tests/test_wheel.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "testing.h" -#include "wheel.h" - -void test_get_wheel_file() { - struct testcase { - const char *filename; - struct Wheel expected; - }; - struct testcase tc[] = { - { - // Test for "build tags" - .filename = "btpackage-1.2.3-mytag-py2.py3-none-any.whl", - .expected = { - .file_name = "btpackage-1.2.3-mytag-py2.py3-none-any.whl", - .version = "1.2.3", - .distribution = "btpackage", - .build_tag = "mytag", - .platform_tag = "any", - .python_tag = "py2.py3", - .abi_tag = "none", - .path_name = ".", - } - }, - { - // Test for universal package format - .filename = "anypackage-1.2.3-py2.py3-none-any.whl", - .expected = { - .file_name = "anypackage-1.2.3-py2.py3-none-any.whl", - .version = "1.2.3", - .distribution = "anypackage", - .build_tag = NULL, - .platform_tag = "any", - .python_tag = "py2.py3", - .abi_tag = "none", - .path_name = ".", - } - }, - { - // Test for binary package format - .filename = "binpackage-1.2.3-cp311-cp311-linux_x86_64.whl", - .expected = { - .file_name = "binpackage-1.2.3-cp311-cp311-linux_x86_64.whl", - .version = "1.2.3", - .distribution = "binpackage", - .build_tag = NULL, - .platform_tag = "linux_x86_64", - .python_tag = "cp311", - .abi_tag = "cp311", - .path_name = ".", - } - }, - }; - - struct Wheel *doesnotexist = get_wheel_info("doesnotexist", "doesnotexist-0.0.1-py2.py3-none-any.whl", (char *[]) {"not", NULL}, WHEEL_MATCH_ANY); - STASIS_ASSERT(doesnotexist == NULL, "returned non-NULL on error"); - - for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { - struct testcase *test = &tc[i]; - struct Wheel *wheel = get_wheel_info(".", test->expected.distribution, (char *[]) {(char *) test->expected.version, NULL}, WHEEL_MATCH_ANY); - STASIS_ASSERT(wheel != NULL, "result should not be NULL!"); - STASIS_ASSERT(wheel->file_name && strcmp(wheel->file_name, test->expected.file_name) == 0, "mismatched file name"); - STASIS_ASSERT(wheel->version && strcmp(wheel->version, test->expected.version) == 0, "mismatched version"); - STASIS_ASSERT(wheel->distribution && strcmp(wheel->distribution, test->expected.distribution) == 0, "mismatched distribution (package name)"); - STASIS_ASSERT(wheel->platform_tag && strcmp(wheel->platform_tag, test->expected.platform_tag) == 0, "mismatched platform tag ([platform]_[architecture])"); - STASIS_ASSERT(wheel->python_tag && strcmp(wheel->python_tag, test->expected.python_tag) == 0, "mismatched python tag (python version)"); - STASIS_ASSERT(wheel->abi_tag && strcmp(wheel->abi_tag, test->expected.abi_tag) == 0, "mismatched abi tag (python compatibility version)"); - if (wheel->build_tag) { - STASIS_ASSERT(strcmp(wheel->build_tag, test->expected.build_tag) == 0, - "mismatched build tag (optional arbitrary string)"); - } - wheel_free(&wheel); - } -} - -int main(int argc, char *argv[]) { - STASIS_TEST_BEGIN_MAIN(); - STASIS_TEST_FUNC *tests[] = { - test_get_wheel_file, - }; - - // Create mock package directories, and files - mkdir("binpackage", 0755); - touch("binpackage/binpackage-1.2.3-cp311-cp311-linux_x86_64.whl"); - mkdir("anypackage", 0755); - touch("anypackage/anypackage-1.2.3-py2.py3-none-any.whl"); - mkdir("btpackage", 0755); - touch("btpackage/btpackage-1.2.3-mytag-py2.py3-none-any.whl"); - - STASIS_TEST_RUN(tests); - STASIS_TEST_END_MAIN(); -} \ No newline at end of file diff --git a/tests/test_wheelinfo.c b/tests/test_wheelinfo.c new file mode 100644 index 0000000..1abbeac --- /dev/null +++ b/tests/test_wheelinfo.c @@ -0,0 +1,91 @@ +#include "testing.h" +#include "wheelinfo.h" + +void test_wheelinfo_get() { + struct testcase { + const char *filename; + struct WheelInfo expected; + }; + struct testcase tc[] = { + { + // Test for "build tags" + .filename = "btpackage-1.2.3-mytag-py2.py3-none-any.whl", + .expected = { + .file_name = "btpackage-1.2.3-mytag-py2.py3-none-any.whl", + .version = "1.2.3", + .distribution = "btpackage", + .build_tag = "mytag", + .platform_tag = "any", + .python_tag = "py2.py3", + .abi_tag = "none", + .path_name = ".", + } + }, + { + // Test for universal package format + .filename = "anypackage-1.2.3-py2.py3-none-any.whl", + .expected = { + .file_name = "anypackage-1.2.3-py2.py3-none-any.whl", + .version = "1.2.3", + .distribution = "anypackage", + .build_tag = NULL, + .platform_tag = "any", + .python_tag = "py2.py3", + .abi_tag = "none", + .path_name = ".", + } + }, + { + // Test for binary package format + .filename = "binpackage-1.2.3-cp311-cp311-linux_x86_64.whl", + .expected = { + .file_name = "binpackage-1.2.3-cp311-cp311-linux_x86_64.whl", + .version = "1.2.3", + .distribution = "binpackage", + .build_tag = NULL, + .platform_tag = "linux_x86_64", + .python_tag = "cp311", + .abi_tag = "cp311", + .path_name = ".", + } + }, + }; + + struct WheelInfo *doesnotexist = wheelinfo_get("doesnotexist", "doesnotexist-0.0.1-py2.py3-none-any.whl", (char *[]) {"not", NULL}, WHEEL_MATCH_ANY); + STASIS_ASSERT(doesnotexist == NULL, "returned non-NULL on error"); + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + struct testcase *test = &tc[i]; + struct WheelInfo *wheel = wheelinfo_get(".", test->expected.distribution, (char *[]) {(char *) test->expected.version, NULL}, WHEEL_MATCH_ANY); + STASIS_ASSERT(wheel != NULL, "result should not be NULL!"); + STASIS_ASSERT(wheel->file_name && strcmp(wheel->file_name, test->expected.file_name) == 0, "mismatched file name"); + STASIS_ASSERT(wheel->version && strcmp(wheel->version, test->expected.version) == 0, "mismatched version"); + STASIS_ASSERT(wheel->distribution && strcmp(wheel->distribution, test->expected.distribution) == 0, "mismatched distribution (package name)"); + STASIS_ASSERT(wheel->platform_tag && strcmp(wheel->platform_tag, test->expected.platform_tag) == 0, "mismatched platform tag ([platform]_[architecture])"); + STASIS_ASSERT(wheel->python_tag && strcmp(wheel->python_tag, test->expected.python_tag) == 0, "mismatched python tag (python version)"); + STASIS_ASSERT(wheel->abi_tag && strcmp(wheel->abi_tag, test->expected.abi_tag) == 0, "mismatched abi tag (python compatibility version)"); + if (wheel->build_tag) { + STASIS_ASSERT(strcmp(wheel->build_tag, test->expected.build_tag) == 0, + "mismatched build tag (optional arbitrary string)"); + } + wheelinfo_free(&wheel); + } +} + +int main(int argc, char *argv[]) { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { + test_wheelinfo_get, + }; + + // Create mock package directories, and files + mkdir("binpackage", 0755); + touch("binpackage/binpackage-1.2.3-cp311-cp311-linux_x86_64.whl"); + mkdir("anypackage", 0755); + touch("anypackage/anypackage-1.2.3-py2.py3-none-any.whl"); + mkdir("btpackage", 0755); + touch("btpackage/btpackage-1.2.3-mytag-py2.py3-none-any.whl"); + + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); +} \ No newline at end of file -- cgit