diff --git a/src/install.c b/src/install.c index c5b35d4..5a1879f 100644 --- a/src/install.c +++ b/src/install.c @@ -151,8 +151,7 @@ static int native_binary_ok(const char *path, PmmOS os) { } int install_file(const char *url, const char *name) { - PmmOS os = pmm_detect_os(); - char cache[1024], path[1200], cmd[2600]; + char cache[1024], path[1200]; pmm_cache_dir(cache, sizeof(cache)); snprintf(path, sizeof(path), "%s/%s", cache, name); @@ -189,6 +188,17 @@ int install_file(const char *url, const char *name) { return -1; } + return install_path(path, name); +} + +/* Install an already-local file path by its extension. Shared by downloads + * (install_file) and local `pmm install -dpkg/-msi ` / `install file.deb`. + * Returns 0 on success. */ +static int install_path(const char *path, const char *name) { + PmmOS os = pmm_detect_os(); + char dest[1024]; + pmm_install_dir(dest, sizeof(dest)); + const char *bname = base_name(name); /* A bare binary (no extension) must really be a native executable for this * OS; otherwise refuse so the caller can fall back to the os-named asset. */ @@ -197,18 +207,16 @@ int install_file(const char *url, const char *name) { if (!native_binary_ok(path, os)) { fprintf(stderr, "pmm: %s is not a usable %s binary; will fall back\n", bname, pmm_os_name(os)); - remove(path); return -1; } } - char dest[1024]; - pmm_install_dir(dest, sizeof(dest)); - /* forward-slash clone of the cached path (native exes/tools accept it and the + /* forward-slash clone of the path (native exes/tools accept it and the * git-bash shell won't mangle a command word like "C:/...") */ char pwin[1200]; snprintf(pwin, sizeof(pwin), "%s", path); for (char *q = pwin; *q; q++) if (*q == '\\') *q = '/'; + char cmd[2600]; /* .pdm packages are installed through the deb-like manager */ if (has_suffix(bname, ".pdm") || has_suffix(bname, ".PDM")) { @@ -276,6 +284,17 @@ int install_file(const char *url, const char *name) { return 0; } +/* Install a local file by its extension — e.g. `pmm install -dpkg foo.deb` + * (Linux) or `pmm install -msi foo.msi` (Windows). Returns 0 on success. */ +int install_local_file(const char *path) { + if (!path || !*path) { fprintf(stderr, "pmm: empty file path\n"); return -1; } + FILE *chk = fopen(path, "rb"); + if (!chk) { fprintf(stderr, "pmm: cannot open file: %s\n", path); return -1; } + fclose(chk); + printf("pmm: installing local file %s\n", path); + return install_path(path, path); +} + /* ---------- python-style version selection (==,>=,<=,>,<,!= , comma list) ---------- */ /* Compare dotted version strings numerically ("24.20.0" vs "24.2.0"). */ diff --git a/src/install.h b/src/install.h index 1ce41fa..5a006c7 100644 --- a/src/install.h +++ b/src/install.h @@ -6,6 +6,10 @@ * name is the asset file name used to decide the install method. */ int install_file(const char *url, const char *name); +/* Install a local file path according to its extension — e.g. + * `pmm install -dpkg foo.deb` (Linux) or `pmm install -msi foo.msi` (Windows). */ +int install_local_file(const char *path); + /* 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/main.c b/src/main.c index b81abe2..e890d56 100644 --- a/src/main.c +++ b/src/main.c @@ -138,6 +138,19 @@ static PmmHost host_from_name(const char *h) { return HOST_UNKNOWN; } +/* True if the filename has a known auto-installable extension (local packages). */ +static int has_installer_ext(const char *s) { + static const char *exts[] = { ".deb", ".rpm", ".appimage", ".msi", ".exe", ".zip", + ".tgz", ".tar.gz", ".tar.xz", ".tar.bz2", ".7z", + ".dmg", ".pkg", ".pkg.tar.zst", NULL }; + size_t l = strlen(s); + for (int i = 0; exts[i]; i++) { + size_t el = strlen(exts[i]); + if (l >= el && strcmp(s + l - el, exts[i]) == 0) return 1; + } + return 0; +} + /* [--host ] */ static int cmd_install_git(int argc, char **argv, const char *flag) { const char *repo = NULL; @@ -397,15 +410,21 @@ int main(int argc, char **argv) { strcmp(argv[2], "--forgejo") == 0)) return cmd_install_git(argc - 3, argv + 3, argv[2] + 2); if (argc < 3) { - fprintf(stderr, "pmm: usage: pmm install [...] | pmm install --git \n"); + fprintf(stderr, "pmm: usage: pmm install [pkg2 ...]\n" + " | pmm install -dpkg install a .deb (Linux)\n" + " | pmm install -msi install an .msi (Windows)\n" + " | pmm install --git \n"); return 1; } /* collect one or more package arguments (each may carry an inline version * spec like nodejs>=24,<25); optional -v/--version applies to a single one */ const char *version = NULL; + int forced = 0; /* 1 = -dpkg, 2 = -msi (install a local package by type) */ const char *items[64]; int ni = 0; for (int i = 2; i < argc; i++) { const char *a = argv[i]; + if (strcmp(a, "-dpkg") == 0) { forced = 1; if (i + 1 < argc) items[ni++] = argv[++i]; continue; } + if (strcmp(a, "-msi") == 0) { forced = 2; if (i + 1 < argc) items[ni++] = argv[++i]; continue; } if (strncmp(a, "-v", 2) == 0 && a[2]) { version = a + 2; continue; } if (strcmp(a, "-v") == 0 && i + 1 < argc) { version = argv[++i]; continue; } if (strcmp(a, "--version") == 0 && i + 1 < argc) { version = argv[++i]; continue; } @@ -443,6 +462,13 @@ int main(int argc, char **argv) { if (pdm_install_file(pkg) == 0) ok++; else fprintf(stderr, "pmm: failed to install %s\n", pkg); continue; } + /* local file install: forced -dpkg/-msi, or an existing installer file + * (e.g. `pmm install foo.deb` / `pmm install foo.msi`) */ + if (forced == 1 || forced == 2 || has_installer_ext(pkg)) { + if (install_local_file(pkg) == 0) ok++; + else fprintf(stderr, "pmm: failed to install %s\n", pkg); + continue; + } if (install_from_registry(pkg, spec[0] ? spec : NULL, mirror.name) == 0) ok++; } free(cfg.registry_url); free(cfg.mirror_name);