feat: dependency resolution (Depends: + registry depends, recursive w/ cycle guard); parallel chunked download + resume (POSIX, failsafe single-stream)

这个提交包含在:
JGZYES
2026-09-02 18:21:45 +08:00
父节点 bd558e5c29
当前提交 c408983206
修改 4 个文件,包含 152 行新增7 行删除
+78 -4
查看文件
@@ -203,23 +203,35 @@ static unsigned long long remote_size(const char *url) {
return sz; 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 http_download(const char *url, const char *out_path) {
int tty = IS_TTY(STDERR_FD); int tty = IS_TTY(STDERR_FD);
if (getenv("PMM_FORCE_PROGRESS")) tty = 1; /* debug/testing override */ if (getenv("PMM_FORCE_PROGRESS")) tty = 1; /* debug/testing override */
if (!tty) { 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; size_t cmdlen = strlen(url) * 3 + strlen(out_path) * 3 + 160;
char *cmd = malloc(cmdlen); char *cmd = malloc(cmdlen);
if (!cmd) return -1; if (cmd) {
snprintf(cmd, cmdlen, "curl -L --fail --retry 3 --max-time 3600 -S --silent -o \"%s\" \"%s\"", out_path, url); snprintf(cmd, cmdlen, "curl -L --fail --retry 3 --max-time 3600 -C - -S --silent -o \"%s\" \"%s\"", out_path, url);
int rc = system(cmd); int rc = system(cmd);
free(cmd); free(cmd);
return (rc == 0) ? 0 : -1; 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); 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]; char cmd[2100];
snprintf(cmd, sizeof(cmd), "curl -sL --fail --retry 3 --max-time 3600 -o - \"%s\"", url); snprintf(cmd, sizeof(cmd), "curl -sL --fail --retry 3 --max-time 3600 -o - \"%s\"", url);
FILE *pf = PMM_POPEN_READ_X(cmd); /* binary read */ 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); render_progress(fetched, total, el, speed, 1);
return 0; 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
}
+64
查看文件
@@ -372,6 +372,51 @@ static const char *cur_arch_name(void) {
return pmm_detect_arch(); 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) { int install_from_registry(const char *name, const char *spec, const char *mirror_active) {
if (!name || !*name) return -1; if (!name || !*name) return -1;
MirrorList *ml = mirrors_load(); MirrorList *ml = mirrors_load();
@@ -436,6 +481,15 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
return -1; 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 *osn = cur_os_name();
const char *arch = cur_arch_name(); const char *arch = cur_arch_name();
JsonValue *variants = json_get(meta, "variants"); 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); free(url_cp); free(file_cp); free(want_sha);
return rc; 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);
}
+3
查看文件
@@ -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). */ /* Set by `pmm install --no-cache` to force a fresh download (drop cache file). */
extern int pmm_no_cache; 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): /* Install from a remote registry (apt-style multi-mirror fallback):
* looks up package `name` in the configured registry mirrors by priority. * looks up package `name` in the configured registry mirrors by priority.
* If `version` is non-NULL, fetches {registry}/{name}/{version}.json, otherwise * If `version` is non-NULL, fetches {registry}/{name}/{version}.json, otherwise
+4
查看文件
@@ -1,6 +1,7 @@
/* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */ /* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */
#include "pdm.h" #include "pdm.h"
#include "pmm.h" #include "pmm.h"
#include "install.h"
#include "sha256.h" #include "sha256.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -440,6 +441,9 @@ int pdm_install_file(const char *pdmfile) {
size_t got = fread(ctl, 1, sizeof(ctl) - 1, cf); size_t got = fread(ctl, 1, sizeof(ctl) - 1, cf);
PMM_PCLOSE_READ(cf); PMM_PCLOSE_READ(cf);
ctl[got] = '\0'; 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 *pkg = control_get(ctl, "Package");
char *ver = control_get(ctl, "Version"); char *ver = control_get(ctl, "Version");
if (!pkg || !*pkg) { if (!pkg || !*pkg) {