From c4089832067ba5edf011810b60d0a7c8060a9e7f Mon Sep 17 00:00:00 2001 From: JGZYES Date: Wed, 2 Sep 2026 18:21:45 +0800 Subject: [PATCH] feat: dependency resolution (Depends: + registry depends, recursive w/ cycle guard); parallel chunked download + resume (POSIX, failsafe single-stream) --- src/http.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++---- src/install.c | 64 +++++++++++++++++++++++++++++++++++++ src/install.h | 3 ++ src/pdm.c | 4 +++ 4 files changed, 152 insertions(+), 7 deletions(-) diff --git a/src/http.c b/src/http.c index ad0dc0d..281ada9 100644 --- a/src/http.c +++ b/src/http.c @@ -203,23 +203,35 @@ static unsigned long long remote_size(const char *url) { return sz; } +static int download_parallel(const char *url, const char *out, unsigned long long total, int threads); + int http_download(const char *url, const char *out_path) { int tty = IS_TTY(STDERR_FD); if (getenv("PMM_FORCE_PROGRESS")) tty = 1; /* debug/testing override */ if (!tty) { - /* background/redirect: silent, simple */ + /* background/redirect: silent, simple (with curl resume -C -) */ size_t cmdlen = strlen(url) * 3 + strlen(out_path) * 3 + 160; char *cmd = malloc(cmdlen); - if (!cmd) return -1; - snprintf(cmd, cmdlen, "curl -L --fail --retry 3 --max-time 3600 -S --silent -o \"%s\" \"%s\"", out_path, url); - int rc = system(cmd); - free(cmd); - return (rc == 0) ? 0 : -1; + if (cmd) { + snprintf(cmd, cmdlen, "curl -L --fail --retry 3 --max-time 3600 -C - -S --silent -o \"%s\" \"%s\"", out_path, url); + int rc = system(cmd); + free(cmd); + return (rc == 0) ? 0 : -1; + } + return -1; } - /* terminal: stream via popen and paint a python-style bar */ + /* terminal: prefer a parallel ranged download (POSIX only) for large files */ unsigned long long total = remote_size(url); +#ifdef _WIN32 + /* no reliable parallel via system() on Windows — use the single stream */ +#else + if (total > 0 && total >= 8u * 1024u * 1024u && download_parallel(url, out_path, total, 4) == 0) + return 0; +#endif + + /* single-stream fallback: stream via popen and paint a python-style bar */ char cmd[2100]; snprintf(cmd, sizeof(cmd), "curl -sL --fail --retry 3 --max-time 3600 -o - \"%s\"", url); FILE *pf = PMM_POPEN_READ_X(cmd); /* binary read */ @@ -251,3 +263,65 @@ int http_download(const char *url, const char *out_path) { render_progress(fetched, total, el, speed, 1); return 0; } + +/* Parallel ranged download with per-part resume (POSIX only; Windows falls back). + * Splits [0,total) into `threads` chunks, fetches each with curl --range into + * out.part.N (resuming an existing partial part), waits, then concatenates and + * verifies the assembled size == total. Returns 0 on success, -1 on any failure + * (the caller then falls back to the single-stream path). */ +static int download_parallel(const char *url, const char *out, unsigned long long total, int threads) { +#ifdef _WIN32 + (void)url; (void)out; (void)total; (void)threads; return -1; +#else + if (total == 0 || threads < 2) return -1; + unsigned long long chunk = (total + (unsigned long long)threads - 1) / (unsigned long long)threads; + char cmdbuf[8192] = ""; + char parts[64][1200]; + int np = 0; + + for (int i = 0; i < threads; i++) { + unsigned long long st = (unsigned long long)i * chunk; + unsigned long long en = st + chunk - 1; + if (st > en || st >= total) break; + if (en >= total) en = total - 1; + char part[1200]; + snprintf(part, sizeof(part), "%s.part.%d", out, i); + snprintf(parts[np], sizeof(parts[0]), "%s", part); + /* resume from the end of an existing partial part */ + long sz = 0; + FILE *pf = fopen(part, "rb"); + if (pf) { fseek(pf, 0, SEEK_END); sz = ftell(pf); fclose(pf); } + unsigned long long fs = st; + if (sz > 0) { fs = st + (unsigned long long)sz; if (fs > en) fs = st; } + if (fs > en) { np++; continue; } /* part already complete */ + char piece[1024]; + snprintf(piece, sizeof(piece), + "curl -sL --fail --retry 2 --max-time 1200 -r %llu-%llu -o \"%s\" \"%s\" &", + fs, en, part, url); + if (strlen(cmdbuf) + strlen(piece) + 4 >= sizeof(cmdbuf)) return -1; + strcat(cmdbuf, piece); + np++; + } + if (np == 0) return -1; + /* wrap in a subshell and wait for all background curls */ + char launch[9000]; + snprintf(launch, sizeof(launch), "( %s wait )", cmdbuf); + if (system(launch) != 0) return -1; + + /* concatenate parts in order and verify size */ + FILE *of = fopen(out, "wb"); + if (!of) return -1; + unsigned long long got = 0; + for (int i = 0; i < np; i++) { + FILE *pf = fopen(parts[i], "rb"); + if (!pf) { fclose(of); return -1; } + char b[65536]; size_t r; + while ((r = fread(b, 1, sizeof(b), pf)) > 0) { fwrite(b, 1, r, of); got += r; } + fclose(pf); + remove(parts[i]); + } + fclose(of); + if (got != total) { remove(out); return -1; } + return 0; +#endif +} diff --git a/src/install.c b/src/install.c index 650a783..7772c14 100644 --- a/src/install.c +++ b/src/install.c @@ -372,6 +372,51 @@ static const char *cur_arch_name(void) { return pmm_detect_arch(); } +/* ---------- dependency resolution (Depends: "name (op ver), ...") ---------- */ +static const char *dep_seen[128]; +static int dep_seen_n = 0; + +static int install_dep_spec(const char *depstr); /* fwd (mutually recursive) */ + +static int parse_dep(const char *d, char *name, size_t ns, char *spec, size_t ss) { + const char *paren = strchr(d, '('); + if (paren) { + size_t nl = (size_t)(paren - d); + while (nl && (d[nl-1]==' '||d[nl-1]=='\t')) nl--; + if (nl >= ns) nl = ns-1; + memcpy(name, d, nl); name[nl] = 0; + const char *c = paren + 1, *ce = strchr(c, ')'); + if (!ce) ce = c + strlen(c); + size_t sl = (size_t)(ce - c); if (sl >= ss) sl = ss-1; + memcpy(spec, c, sl); spec[sl] = 0; + } else { + size_t nl = strlen(d); + while (nl && (d[nl-1]==' '||d[nl-1]=='\t')) nl--; + if (nl >= ns) nl = ns-1; + memcpy(name, d, nl); name[nl] = 0; spec[0] = 0; + } + char *p = name; while (*p==' '||*p=='\t') p++; + if (p != name) memmove(name, p, strlen(p)+1); + int se = (int)strlen(spec); while (se && (spec[se-1]==' '||spec[se-1]=='\t')) spec[--se]=0; + return name[0] ? 0 : -1; +} + +/* Install a comma-separated Depends list. Exposed so local .pdm installs resolve deps. */ +int pmm_install_dep_list(const char *list) { + if (!list || !*list) return 0; + char *dup = strdup(list), *p = dup; + while (p && *p) { + char *end = strchr(p, ','); + if (end) *end = 0; + char *t = p; while (*t==' '||*t=='\t') t++; + if (*t) install_dep_spec(t); + if (!end) break; + p = end + 1; + } + free(dup); + return 0; +} + int install_from_registry(const char *name, const char *spec, const char *mirror_active) { if (!name || !*name) return -1; MirrorList *ml = mirrors_load(); @@ -436,6 +481,15 @@ int install_from_registry(const char *name, const char *spec, const char *mirror return -1; } + /* resolve declared dependencies before installing this package */ + JsonValue *deps = json_get(meta, "depends"); + if (deps && deps->type == JSON_ARRAY) { + for (int i = 0; i < deps->count; i++) { + JsonValue *dv = json_at(deps, i); + if (dv && dv->type == JSON_STRING) install_dep_spec(dv->string); + } + } + const char *osn = cur_os_name(); const char *arch = cur_arch_name(); JsonValue *variants = json_get(meta, "variants"); @@ -499,3 +553,13 @@ int install_from_registry(const char *name, const char *spec, const char *mirror free(url_cp); free(file_cp); free(want_sha); return rc; } + +static int install_dep_spec(const char *depstr) { + char name[256], spec[256]; + if (parse_dep(depstr, name, sizeof(name), spec, sizeof(spec)) != 0) return -1; + for (int i = 0; i < dep_seen_n; i++) + if (strcmp(dep_seen[i], name) == 0) return 0; /* cycle / duplicate */ + if (dep_seen_n < 128) dep_seen[dep_seen_n++] = strdup(name); + printf("pmm: resolving dependency %s%s%s\n", name, spec[0] ? " " : "", spec); + return install_from_registry(name, spec[0] ? spec : NULL, NULL); +} diff --git a/src/install.h b/src/install.h index a223349..1f6be3a 100644 --- a/src/install.h +++ b/src/install.h @@ -13,6 +13,9 @@ int install_local_file(const char *path); /* Set by `pmm install --no-cache` to force a fresh download (drop cache file). */ extern int pmm_no_cache; +/* Resolve a comma-separated Depends list ("a (>= 1.0), b") from the registry. */ +int pmm_install_dep_list(const char *list); + /* Install from a remote registry (apt-style multi-mirror fallback): * looks up package `name` in the configured registry mirrors by priority. * If `version` is non-NULL, fetches {registry}/{name}/{version}.json, otherwise diff --git a/src/pdm.c b/src/pdm.c index 9ea0114..e79a308 100644 --- a/src/pdm.c +++ b/src/pdm.c @@ -1,6 +1,7 @@ /* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */ #include "pdm.h" #include "pmm.h" +#include "install.h" #include "sha256.h" #include #include @@ -440,6 +441,9 @@ int pdm_install_file(const char *pdmfile) { size_t got = fread(ctl, 1, sizeof(ctl) - 1, cf); PMM_PCLOSE_READ(cf); ctl[got] = '\0'; + /* resolve declared dependencies from the registry before installing */ + char *deps = control_get(ctl, "Depends"); + if (deps && *deps) { pmm_install_dep_list(deps); free(deps); } char *pkg = control_get(ctl, "Package"); char *ver = control_get(ctl, "Version"); if (!pkg || !*pkg) {