color: structured [PMM]:[LEVEL] output (error=red/success=green/info=gray/warn=yellow)

- Add src/out.c + src/out.h: pmm_error/pmm_success/pmm_info/pmm_warn helpers.
  Each prefixes '[PMM]:[LEVEL]' and, only when the stream is a tty, wraps it
  in an ANSI colour (red/green/gray/yellow). On redirect/pipe the text is plain.
- Replace all error/success/info/warn print statements across main.c, install.c,
  pdm.c, pmm.c, json.c with these helpers (http.c download progress bar left
  untouched so its \r overwrite stays intact).
- Windows: enable ENABLE_VIRTUAL_TERMINAL_PROCESSING on stdout/stderr so ANSI
  colours render (added alongside the existing SetConsoleOutputCP).
- Rewrite 3 install.c system() error-echo strings to colored \033[31m echoes.
- Build: add src/out.c to Makefile and build.bat.

Verified on WSL gcc: compiles clean. tty (script) shows ESC-coloured output for
INFO/ERROR/SUCCESS; non-tty shows plain '[PMM]:[LEVEL]' text with no escapes.
这个提交包含在:
JGZYES
2026-09-04 19:23:27 +08:00
父节点 7d8e929d19
当前提交 0be951c864
修改 10 个文件,包含 213 行新增116 行删除
+3
查看文件
@@ -17,6 +17,9 @@
.vscode/
.idea/
# tool workspace cache
.zcode/
# The mirror registry is committed so the mirror is complete and downloadable.
# Un-ignore the .pdm files there (*.pdm elsewhere stays ignored). Uses ** so it
# still matches after the mirror moved to web/mirror/.
+1 -1
查看文件
@@ -3,7 +3,7 @@ CFLAGS ?= -O2 -Wall -static -Wextra -std=c11
PREFIX ?= /usr/local
SRC = src/main.c src/json.c src/ini.c src/pmm.c src/http.c src/repo.c \
src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c
src/install.c src/sha256.c src/sha1.c src/mirrors.c src/pdm.c src/out.c
all: pmm
+1 -1
查看文件
@@ -1,6 +1,6 @@
@echo off
rem One-shot Windows build: produces pmm.exe (needs MinGW gcc + curl in PATH)
set SRC=src\main.c src\json.c src\ini.c src\pmm.c src\http.c src\repo.c src\install.c src\sha256.c src\sha1.c src\mirrors.c src\pdm.c
set SRC=src\main.c src\json.c src\ini.c src\pmm.c src\http.c src\repo.c src\install.c src\sha256.c src\sha1.c src\mirrors.c src\pdm.c src\out.c
gcc -O2 -Wall -static -Wextra -std=c11 -o pmm.exe %SRC%
if errorlevel 1 (echo pmm build failed & exit /b 1)
echo built pmm.exe
+42 -41
查看文件
@@ -13,6 +13,7 @@
*/
#include "install.h"
#include "pmm.h"
#include "out.h"
#include "http.h"
#include "json.h"
#include "sha256.h"
@@ -116,16 +117,16 @@ static int verify_checksums(const char *url, const char *path, const char *name)
extract_hex(text, name, expect, sizeof(expect));
free(text);
if (!expect[0]) {
printf("pmm: warning: could not parse %s checksum file\n", algos[i].ext);
pmm_warn("warning: could not parse %s checksum file\n", algos[i].ext);
continue;
}
/* case-insensitive compare */
if (strcasecmp(expect, hex) != 0) {
fprintf(stderr, "pmm: CHECKSUM MISMATCH (%s)!\n expected: %s\n actual: %s\n",
pmm_error("CHECKSUM MISMATCH (%s)!\n expected: %s\n actual: %s\n",
algos[i].ext, expect, hex);
return -1;
}
printf("pmm: %s checksum OK: %s\n", algos[i].algo == 256 ? "sha256" : "sha1", hex);
pmm_success("%s checksum OK: %s\n", algos[i].algo == 256 ? "sha256" : "sha1", hex);
}
return 0;
}
@@ -418,29 +419,29 @@ int install_file(const char *url, const char *name) {
char **cands = mirrors_download_candidates(ml, url, &ncand);
int ok = -1;
for (int i = 0; i < ncand; i++) {
printf("pmm: downloading %s%s\n", cands[i],
pmm_info("downloading %s%s\n", cands[i],
i < ncand - 1 ? " (mirror)" : "");
if (http_download(cands[i], path) == 0) { ok = 0; break; }
fprintf(stderr, "pmm: download failed, trying next source...\n");
pmm_warn("download failed, trying next source...\n");
remove(path);
}
for (int i = 0; i < ncand; i++) free(cands[i]);
free(cands);
mirrors_free(ml);
if (ok != 0) {
fprintf(stderr, "pmm: all download sources failed: %s\n", url);
pmm_error("all download sources failed: %s\n", url);
return -1;
}
printf("pmm: downloaded %s\n", path);
pmm_success("downloaded %s\n", path);
/* integrity: sha256 + sha1, verified against sidecar checksums when present */
char hex[128];
if (pmm_sha256_file(path, hex) == 0)
printf("pmm: sha256: %s\n", hex);
pmm_info("sha256: %s\n", hex);
if (pmm_sha1_file(path, hex) == 0)
printf("pmm: sha1: %s\n", hex);
pmm_info("sha1: %s\n", hex);
if (verify_checksums(url, path, name) != 0) {
fprintf(stderr, "pmm: refusing to install: checksum verification failed\n");
pmm_error("refusing to install: checksum verification failed\n");
remove(path);
return -1;
}
@@ -460,9 +461,9 @@ static int install_path(const char *path, const char *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. */
if (strchr(bname, '.') == NULL) {
printf("pmm: verifying %s is a native %s binary...\n", bname, pmm_os_name(os));
pmm_info("verifying %s is a native %s binary...\n", bname, pmm_os_name(os));
if (!native_binary_ok(path, os)) {
fprintf(stderr, "pmm: %s is not a usable %s binary; will fall back\n",
pmm_warn("%s is not a usable %s binary; will fall back\n",
bname, pmm_os_name(os));
return -1;
}
@@ -478,12 +479,12 @@ static int install_path(const char *path, const char *name) {
/* .pdm packages are installed through the deb-like manager */
if (has_suffix(bname, ".pdm") || has_suffix(bname, ".PDM")) {
extern int pdm_install_file(const char *path);
printf("pmm: installing .pdm %s ...\n", bname);
pmm_info("installing .pdm %s ...\n", bname);
if (pdm_install_file(path) != 0) {
fprintf(stderr, "pmm: .pdm install failed: %s\n", bname);
pmm_error(".pdm install failed: %s\n", bname);
return -1;
}
printf("pmm: installed .pdm %s\n", bname);
pmm_success("installed .pdm %s\n", bname);
pmm_add_to_path();
return 0;
}
@@ -507,7 +508,7 @@ static int install_path(const char *path, const char *name) {
"elif command -v ar >/dev/null 2>&1; then T=$(mktemp -d); "
"(cd \"$T\" && ar p \"%s\" data.tar.gz | tar -xzf -); sudo cp -a \"$T\"/. /; rm -rf \"$T\"; "
"elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; "
"else echo 'pmm: cannot unpack .deb (need tar)' 1>&2; exit 1; fi",
"else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .deb (need tar)\\033[0m\" 1>&2; exit 1; fi",
path, path, path);
}
} else if (has_suffix(bname, ".rpm") && os == OS_LINUX) {
@@ -550,9 +551,9 @@ static int install_path(const char *path, const char *name) {
"if command -v rpm >/dev/null 2>&1; then sudo rpm -Uvh \"%s\"; "
"elif command -v rpm2cpio >/dev/null 2>&1; then sudo rpm2cpio \"%s\" | (cd / && sudo cpio -idm --quiet); "
"elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; "
"else echo 'pmm: cannot unpack .rpm (need rpm/cpio)' 1>&2; exit 1; fi",
"else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .rpm (need rpm/cpio)\\033[0m\" 1>&2; exit 1; fi",
path, path, path);
if (system(cmd) != 0) { fprintf(stderr, "pmm: rpm install failed\n"); return -1; }
if (system(cmd) != 0) { pmm_error("rpm install failed\n"); return -1; }
(void)dcmd; (void)ct;
#else
/* create the (possibly multi-level) stage dir first; pmm_cpio_unpack
@@ -562,18 +563,18 @@ static int install_path(const char *path, const char *name) {
char mkstag[1400];
snprintf(mkstag, sizeof(mkstag), "mkdir -p \"%s\"", fstage);
if (system(mkstag) != 0) {
fprintf(stderr, "pmm: cannot create rpm stage dir: %s\n", fstage);
pmm_error("cannot create rpm stage dir: %s\n", fstage);
return -1;
}
}
/* unpack into stage (no sudo needed; stage is user-writable) */
if (pmm_cpio_unpack_cmd(fstage, dcmd) != 0) {
fprintf(stderr, "pmm: rpm payload decompress/cpio failed (need %s)\n", dc);
pmm_error("rpm payload decompress/cpio failed (need %s)\n", dc);
return -1;
}
/* copy the unpacked payload into /, then clean up */
if (system(cmd) != 0) {
fprintf(stderr, "pmm: failed to copy rpm payload into /\n");
pmm_error("failed to copy rpm payload into /\n");
return -1;
}
(void)ct;
@@ -583,7 +584,7 @@ static int install_path(const char *path, const char *name) {
"if command -v rpm >/dev/null 2>&1; then sudo rpm -Uvh \"%s\"; "
"elif command -v rpm2cpio >/dev/null 2>&1; then sudo rpm2cpio \"%s\" | (cd / && sudo cpio -idm --quiet); "
"elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; "
"else echo 'pmm: cannot unpack .rpm (need gzip/cpio)' 1>&2; exit 1; fi",
"else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .rpm (need gzip/cpio)\\033[0m\" 1>&2; exit 1; fi",
path, path, path);
}
} else if (has_suffix(bname, ".apk") && os == OS_LINUX) {
@@ -635,12 +636,12 @@ static int install_path(const char *path, const char *name) {
snprintf(cmd, sizeof(cmd), "cp -f \"%s\" \"%s/\" 2>/dev/null || true", path, dest);
}
printf("pmm: installing %s ...\n", bname);
pmm_info("installing %s ...\n", bname);
if (run_cmd_quiet(cmd) != 0) {
fprintf(stderr, "pmm: install command failed: %s\n", cmd);
pmm_error("install command failed: %s\n", cmd);
return -1;
}
printf("pmm: installed %s\n", bname);
pmm_success("installed %s\n", bname);
pmm_add_to_path();
return 0;
}
@@ -648,11 +649,11 @@ static int install_path(const char *path, const char *name) {
/* 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; }
if (!path || !*path) { pmm_error("empty file path\n"); return -1; }
FILE *chk = fopen(path, "rb");
if (!chk) { fprintf(stderr, "pmm: cannot open file: %s\n", path); return -1; }
if (!chk) { pmm_error("cannot open file: %s\n", path); return -1; }
fclose(chk);
printf("pmm: installing local file %s\n", path);
pmm_info("installing local file %s\n", path);
return install_path(path, path);
}
@@ -781,8 +782,8 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
if (ml->items[i].registry && *ml->items[i].registry) has_reg = 1;
if (!has_reg) {
fprintf(stderr,
"pmm: no registry mirror configured. Add to ~/.pmm/mirror.ini:\n"
pmm_error(
"no registry mirror configured. Add to ~/.pmm/mirror.ini:\n"
" [name]\n registry = https://host/pmm\n"
" (apt-style: mirrors are tried by priority; the first with the\n"
" package wins, else plain 'pmm install --git owner/repo')\n");
@@ -804,7 +805,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
if (!seen[i] && ml->items[i].registry && *ml->items[i].registry)
bases[nb++] = ml->items[i].registry;
if (nb == 0) { fprintf(stderr, "pmm: no registry mirrors\n"); mirrors_free(ml); return -1; }
if (nb == 0) { pmm_error("no registry mirrors\n"); mirrors_free(ml); return -1; }
/* fetch the <pkg>.json latest pointer (carries the variants list) */
char url[2048];
@@ -813,7 +814,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
char *used_base = NULL;
for (int i = 0; i < nb; i++) {
snprintf(url, sizeof(url), "%s/%s.json", bases[i], name);
printf("pmm: looking up %s in mirror %s\n", name, bases[i]);
pmm_info("looking up %s in mirror %s\n", name, bases[i]);
body = http_get(url, &status);
if (getenv("PMM_DEBUG")) fprintf(stderr, "[reg] %s -> body=%s status=%d\n",
bases[i], body ? "set" : "NULL", status);
@@ -824,14 +825,14 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
free(body); body = NULL;
}
if (!body) {
fprintf(stderr, "pmm: package '%s' not found in any registry mirror\n", name);
pmm_error("package '%s' not found in any registry mirror\n", name);
mirrors_free(ml);
return -1;
}
JsonValue *meta = json_parse(body);
free(body);
if (!meta || meta->type != JSON_OBJECT) {
fprintf(stderr, "pmm: bad registry entry for '%s' (%s)\n", name, used_base);
pmm_error("bad registry entry for '%s' (%s)\n", name, used_base);
json_free(meta);
mirrors_free(ml);
return -1;
@@ -869,19 +870,19 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
const char *s = json_str(v, "sha256"); free(want_sha); want_sha = s ? strdup(s) : NULL; }
}
if (!chosen) {
fprintf(stderr, "pmm: no version of '%s' for os=%s arch=%s%s%s\n", name, osn, arch,
pmm_error("no version of '%s' for os=%s arch=%s%s%s\n", name, osn, arch,
(spec && *spec) ? " satisfying '" : "", (spec && *spec) ? spec : "");
json_free(meta); mirrors_free(ml); return -1;
}
printf("pmm: selected %s@%s (os=%s arch=%s)\n", name, chosen, osn, arch);
pmm_info("selected %s@%s (os=%s arch=%s)\n", name, chosen, osn, arch);
} else {
/* legacy single-platform entry */
dl = json_str(meta, "url"); file = json_str(meta, "file");
const char *s = json_str(meta, "sha256"); want_sha = s ? strdup(s) : NULL;
printf("pmm: selected %s@%s (os=%s)\n", name, json_str(meta, "version"), osn);
pmm_info("selected %s@%s (os=%s)\n", name, json_str(meta, "version"), osn);
}
if (!dl) {
fprintf(stderr, "pmm: registry entry for '%s' has no url\n", name);
pmm_error("registry entry for '%s' has no url\n", name);
json_free(meta); mirrors_free(ml); return -1;
}
char *url_cp = strdup(dl);
@@ -897,12 +898,12 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
snprintf(path, sizeof(path), "%s/%s", cache, file_cp);
if (pmm_sha256_file(path, hex) == 0) {
if (strcasecmp(want_sha, hex) != 0) {
fprintf(stderr, "pmm: CHECKSUM MISMATCH (%s registry entry)!\n"
pmm_error("CHECKSUM MISMATCH (%s registry entry)!\n"
" expected: %s\n actual: %s\n", name, want_sha, hex);
remove(path);
rc = -1;
} else {
printf("pmm: registry sha256 OK: %s\n", hex);
pmm_success("registry sha256 OK: %s\n", hex);
}
}
}
@@ -916,6 +917,6 @@ static int install_dep_spec(const char *depstr) {
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);
pmm_info("resolving dependency %s%s%s\n", name, spec[0] ? " " : "", spec);
return install_from_registry(name, spec[0] ? spec : NULL, NULL);
}
+4 -3
查看文件
@@ -1,5 +1,6 @@
/* json.c - minimal recursive-descent JSON parser (no external deps) */
#include "json.h"
#include "out.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -11,7 +12,7 @@ static JsonValue *parse_value(Parser *ps);
static JsonValue *jnew(JsonType t) {
JsonValue *v = calloc(1, sizeof(JsonValue));
if (!v) { fprintf(stderr, "pmm: out of memory\n"); exit(1); }
if (!v) { pmm_error("out of memory\n"); exit(1); }
v->type = t;
return v;
}
@@ -35,7 +36,7 @@ static void jadd(JsonValue *v, char *key, JsonValue *item) {
if (v->type == JSON_OBJECT)
v->keys = realloc(v->keys, v->capacity * sizeof(char *));
if (!v->items || (v->type == JSON_OBJECT && !v->keys)) {
fprintf(stderr, "pmm: out of memory\n"); exit(1);
pmm_error("out of memory\n"); exit(1);
}
}
if (v->type == JSON_OBJECT) v->keys[v->count] = key;
@@ -47,7 +48,7 @@ static void skip_ws(Parser *ps) {
}
static void fail(const char *msg) {
fprintf(stderr, "pmm: JSON parse error: %s\n", msg);
pmm_error("JSON parse error: %s\n", msg);
exit(1);
}
+56 -40
查看文件
@@ -29,6 +29,7 @@
#include <ctype.h>
#include "pmm.h"
#include "out.h"
#include "json.h"
#include "ini.h"
#include "http.h"
@@ -194,13 +195,13 @@ static char *registry_fetch(const char *rel, int *outstatus) {
/* pmm search <keyword> — list registry packages matching keyword. */
static int cmd_search(int argc, char **argv) {
if (argc < 1) { fprintf(stderr, "pmm: usage: pmm search <keyword>\n"); return 1; }
if (argc < 1) { pmm_error("usage: pmm search <keyword>\n"); return 1; }
int status = 0;
char *body = registry_fetch("packages.json", &status);
if (!body) { fprintf(stderr, "pmm: no registry index (packages.json) available\n"); return 1; }
if (!body) { pmm_error("no registry index (packages.json) available\n"); return 1; }
JsonValue *root = json_parse(body);
free(body);
if (!root || root->type != JSON_ARRAY) { fprintf(stderr, "pmm: bad registry index\n"); json_free(root); return 1; }
if (!root || root->type != JSON_ARRAY) { pmm_error("bad registry index\n"); json_free(root); return 1; }
const char *kw = argv[0];
int found = 0;
for (int i = 0; i < root->count; i++) {
@@ -210,13 +211,13 @@ static int cmd_search(int argc, char **argv) {
if (ci_contains(name, kw)) { printf(" %s\n", name); found++; }
}
json_free(root);
if (!found) printf("pmm: no package matches '%s'\n", kw);
if (!found) pmm_info("no package matches '%s'\n", kw);
return 0;
}
/* pmm info <package|file.pdm> — registry package info, or a local .pdm's control. */
static int cmd_info(int argc, char **argv) {
if (argc < 1) { fprintf(stderr, "pmm: usage: pmm info <package|file.pdm>\n"); return 1; }
if (argc < 1) { pmm_error("usage: pmm info <package|file.pdm>\n"); return 1; }
const char *pkg = argv[0];
/* local .pdm -> control info */
FILE *chk = fopen(pkg, "rb");
@@ -230,13 +231,13 @@ static int cmd_info(int argc, char **argv) {
char rel[512]; snprintf(rel, sizeof(rel), "%s.json", pkg);
int status = 0;
char *body = registry_fetch(rel, &status);
if (!body) { fprintf(stderr, "pmm: package '%s' not found in registry\n", pkg); return 1; }
if (!body) { pmm_error("package '%s' not found in registry\n", pkg); return 1; }
JsonValue *root = json_parse(body);
free(body);
if (!root || root->type != JSON_OBJECT) { fprintf(stderr, "pmm: bad registry entry '%s'\n", pkg); json_free(root); return 1; }
if (!root || root->type != JSON_OBJECT) { pmm_error("bad registry entry '%s'\n", pkg); json_free(root); return 1; }
const char *name = json_str(root, "name");
const char *ver = json_str(root, "version");
printf("pmm: %s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : "");
pmm_info("%s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : "");
JsonValue *vr = json_get(root, "variants");
if (vr && vr->count > 0) {
for (int i = 0; i < vr->count; i++) {
@@ -257,12 +258,12 @@ static int cmd_info(int argc, char **argv) {
/* pmm verify <file> — print sha256 (and sha1) of a downloaded package file. */
static int cmd_verify(int argc, char **argv) {
if (argc < 1) { fprintf(stderr, "pmm: usage: pmm verify <file>\n"); return 1; }
if (argc < 1) { pmm_error("usage: pmm verify <file>\n"); return 1; }
const char *file = argv[0];
char hex[128];
if (pmm_sha256_file(file, hex) == 0) printf("pmm: sha256 %s %s\n", hex, file);
else { fprintf(stderr, "pmm: cannot read %s\n", file); return 1; }
if (pmm_sha1_file(file, hex) == 0) printf("pmm: sha1 %s %s\n", hex, file);
if (pmm_sha256_file(file, hex) == 0) pmm_success("sha256 %s %s\n", hex, file);
else { pmm_error("cannot read %s\n", file); return 1; }
if (pmm_sha1_file(file, hex) == 0) pmm_success("sha1 %s %s\n", hex, file);
return 0;
}
@@ -301,7 +302,7 @@ static void rm_tree(const char *dir) {
static int cmd_cache_clean(void) {
char dir[1024];
pmm_cache_dir(dir, sizeof(dir));
printf("pmm: cleaning cache %s\n", dir);
pmm_info("cleaning cache %s\n", dir);
rm_tree(dir);
return 0;
}
@@ -324,7 +325,7 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
if (strcmp(argv[i], "--host") == 0 && i + 1 < argc) {
host = host_from_name(argv[++i]);
if (host == HOST_UNKNOWN) {
fprintf(stderr, "pmm: unknown host '%s'\n", argv[i]);
pmm_error("unknown host '%s'\n", argv[i]);
return 1;
}
} else if ((strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--branch") == 0) && i + 1 < argc) {
@@ -334,8 +335,8 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
}
}
if (!repo) {
fprintf(stderr,
"pmm: usage:\n"
pmm_error(
"usage:\n"
" pmm install --git <repo-url.git> any git host (auto-detected API)\n"
" pmm install --github <owner/repo> GitHub\n"
" pmm install --gitlab <owner/repo> GitLab\n"
@@ -348,22 +349,22 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
RepoContext *ctx = repo_open(host, repo, mirror.api_base);
if (!ctx) return 1;
if (branch && *branch) { free(ctx->ref); ctx->ref = strdup(branch); }
printf("pmm: host=%s repo=%s%s%s\n", host_name(host), repo,
pmm_info("host=%s repo=%s%s%s\n", host_name(host), repo,
mirror.name ? " mirror=" : "", mirror.name ? mirror.name : "");
PmmOS os = pmm_detect_os();
ReleaseAsset *asset = repo_latest_asset(ctx, os);
if (!asset && host == HOST_AUTO)
fprintf(stderr, "pmm: could not reach a known release API for %s "
pmm_warn("could not reach a known release API for %s "
"(tried gitea/gitlab/github shapes)\n", repo);
if (!asset) {
if (host != HOST_AUTO)
fprintf(stderr, "pmm: no suitable %s asset found in latest release of %s\n",
pmm_error("no suitable %s asset found in latest release of %s\n",
pmm_os_name(os), repo);
repo_close(ctx);
return 1;
}
printf("pmm: %s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag);
pmm_info("%s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag);
int rc = install_file(asset->url, asset->name);
/* fallback: the first pick may have been a cross-platform/wrong asset
@@ -374,7 +375,7 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
if (sub) {
ReleaseAsset *fb = repo_asset_matching(ctx, os, sub);
if (fb && strcmp(fb->name, asset->name) != 0) {
printf("pmm: retrying with %s (%s)\n", fb->name, pmm_os_name(os));
pmm_warn("retrying with %s (%s)\n", fb->name, pmm_os_name(os));
rc = install_file(fb->url, fb->name);
repo_asset_free(fb);
}
@@ -413,10 +414,10 @@ static int cmd_mirror(int argc, char **argv) {
}
if (strcmp(argv[0], "add") == 0 && argc >= 3) {
FILE *f = fopen(path, "a");
if (!f) { fprintf(stderr, "pmm: cannot write %s\n", path); return 1; }
if (!f) { pmm_error("cannot write %s\n", path); return 1; }
fprintf(f, "\n[%s]\napi = %s\n", argv[1], argv[2]);
fclose(f);
printf("pmm: mirror '%s' added\n", argv[1]);
pmm_success("mirror '%s' added\n", argv[1]);
return 0;
}
if (strcmp(argv[0], "use") == 0 && argc >= 2) {
@@ -433,20 +434,20 @@ static int cmd_mirror(int argc, char **argv) {
fclose(rf);
}
FILE *wf = fopen(cfgpath, "w");
if (!wf) { fprintf(stderr, "pmm: cannot write %s\n", cfgpath); return 1; }
if (!wf) { pmm_error("cannot write %s\n", cfgpath); return 1; }
int wrote = 0;
for (int i = 0; i < n; i++) fputs(lines[i], wf);
fprintf(wf, "mirror = %s\n", argv[1]);
(void)wrote;
fclose(wf);
printf("pmm: active mirror set to '%s'\n", argv[1]);
pmm_success("active mirror set to '%s'\n", argv[1]);
return 0;
}
if (strcmp(argv[0], "remove") == 0 && argc >= 2) {
Ini *ini = ini_load(path);
if (!ini) { fprintf(stderr, "pmm: no mirror file\n"); return 1; }
if (!ini) { pmm_error("no mirror file\n"); return 1; }
FILE *f = fopen(path, "w");
if (!f) { fprintf(stderr, "pmm: cannot write %s\n", path); ini_free(ini); return 1; }
if (!f) { pmm_error("cannot write %s\n", path); ini_free(ini); return 1; }
char cursec[512] = "";
for (IniEntry *e = ini->head; e; e = e->next) {
if (strcmp(e->section, cursec) != 0) {
@@ -459,10 +460,10 @@ static int cmd_mirror(int argc, char **argv) {
}
fclose(f);
ini_free(ini);
printf("pmm: mirror '%s' removed\n", argv[1]);
pmm_success("mirror '%s' removed\n", argv[1]);
return 0;
}
fprintf(stderr, "pmm: usage: pmm mirror list|add|use|remove ...\n");
pmm_error("usage: pmm mirror list|add|use|remove ...\n");
return 1;
}
@@ -534,6 +535,21 @@ int main(int argc, char **argv) {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8); /* UTF-8 output so Chinese help/version isn't mojibake */
setvbuf(stdout, NULL, _IONBF, 0);
/* Enable ANSI/VT colour on modern Windows consoles (legacy gets it too via
* ENABLE_VIRTUAL_TERMINAL_PROCESSING). Without this, "\033[31m" shows as
* literal text. Do it for both stdout and stderr. */
{
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
if (h && h != INVALID_HANDLE_VALUE) {
DWORD m = 0;
if (GetConsoleMode(h, &m)) SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
h = GetStdHandle(STD_ERROR_HANDLE);
if (h && h != INVALID_HANDLE_VALUE) {
DWORD m = 0;
if (GetConsoleMode(h, &m)) SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
#endif
if (argc < 2) { print_help(); return 0; }
pmm_set_self_path(argv[0]);
@@ -544,14 +560,14 @@ int main(int argc, char **argv) {
print_help(); return 0;
}
if (strcmp(argv[1], "version") == 0 || strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0) {
printf("pmm %s (%s) [%s]\n", PMM_VERSION, pmm_os_name(pmm_detect_os()), pmm_detect_arch());
pmm_info("pmm %s (%s) [%s]\n", PMM_VERSION, pmm_os_name(pmm_detect_os()), pmm_detect_arch());
return 0;
}
if (strcmp(argv[1], "list") == 0) { pdm_list_installed(); return 0; }
if (strcmp(argv[1], "mirror") == 0) return cmd_mirror(argc - 2, argv + 2);
/* pmm remove <pkg> (also used by the Windows "Uninstall" registration) */
if (strcmp(argv[1], "remove") == 0) {
if (argc < 3) { fprintf(stderr, "pmm: usage: pmm remove <pkg>\n"); return 1; }
if (argc < 3) { pmm_error("usage: pmm remove <pkg>\n"); return 1; }
return pdm_remove(argv[2]) == 0 ? 0 : 1;
}
/* pmm self-update: install the latest 'pmm' tool package (auto os+arch) */
@@ -559,13 +575,13 @@ int main(int argc, char **argv) {
PmmConfig cfg; MirrorSel mirror;
load_config(&cfg);
load_mirror(&cfg, &mirror);
printf("pmm: self-update -> installing latest pmm (%s/%s)\n",
pmm_info("self-update -> installing latest pmm (%s/%s)\n",
pmm_os_name(pmm_detect_os()), pmm_detect_arch());
int rc = install_from_registry("pmm", NULL, mirror.name);
free(cfg.registry_url); free(cfg.mirror_name);
free(mirror.name); free(mirror.api_base);
if (rc == 0)
printf("pmm: updated. New tools are under %s/root/bin (pmm/pdm). Re-run from there, or add it to PATH.\n",
pmm_success("updated. New tools are under %s/root/bin (pmm/pdm). Re-run from there, or add it to PATH.\n",
pmm_config_dir((char[1024]){0}, 1024));
return rc == 0 ? 0 : 1;
}
@@ -576,7 +592,7 @@ 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 <pkg|file.deb|file.msi|file.rpm|file.pdm> [pkg2 ...]\n"
pmm_error("usage: pmm install <pkg|file.deb|file.msi|file.rpm|file.pdm> [pkg2 ...]\n"
" | pmm install -dpkg <x.deb> install a .deb (Linux)\n"
" | pmm install -rpm <x.rpm> install an .rpm (any Linux, incl. Debian/RPM)\n"
" | pmm install --git <repo>\n");
@@ -627,7 +643,7 @@ int main(int argc, char **argv) {
}
size_t la = strlen(pkg);
if (la > 4 && (strcmp(pkg + la - 4, ".pdm") == 0 || strcmp(pkg + la - 4, ".PDM") == 0)) {
if (pdm_install_file(pkg) == 0) ok++; else fprintf(stderr, "pmm: failed to install %s\n", pkg);
if (pdm_install_file(pkg) == 0) ok++; else pmm_error("failed to install %s\n", pkg);
continue;
}
/* direct URL install: pmm install https://.../foo.zip
@@ -639,14 +655,14 @@ int main(int argc, char **argv) {
else if (forced == 2) bn = "download.msi";
else if (forced == 3) bn = "download.rpm";
if (install_file(pkg, bn) == 0) ok++;
else fprintf(stderr, "pmm: failed to install %s\n", pkg);
else pmm_error("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 || forced == 3 || has_installer_ext(pkg)) {
if (install_local_file(pkg) == 0) ok++;
else fprintf(stderr, "pmm: failed to install %s\n", pkg);
else pmm_error("failed to install %s\n", pkg);
continue;
}
if (install_from_registry(pkg, spec[0] ? spec : NULL, mirror.name) == 0) ok++;
@@ -658,7 +674,7 @@ int main(int argc, char **argv) {
if (strcmp(argv[1], "pack") == 0) {
if (argc < 3) {
fprintf(stderr, "pmm: usage: pmm pack <dir> [output.pdm]\n");
pmm_error("usage: pmm pack <dir> [output.pdm]\n");
return 1;
}
return pdm_pack(argv[2], argc >= 4 ? argv[3] : NULL) == 0 ? 0 : 1;
@@ -668,13 +684,13 @@ int main(int argc, char **argv) {
return cmd_search(argc - 2, argv + 2);
if (strcmp(argv[1], "cache") == 0) {
if (argc >= 3 && strcmp(argv[2], "clean") == 0) return cmd_cache_clean();
fprintf(stderr, "pmm: usage: pmm cache clean\n"); return 1;
pmm_error("usage: pmm cache clean\n"); return 1;
}
if (strcmp(argv[1], "info") == 0)
return cmd_info(argc - 2, argv + 2);
if (strcmp(argv[1], "verify") == 0)
return cmd_verify(argc - 2, argv + 2);
fprintf(stderr, "pmm: unknown command '%s' (try 'pmm help')\n", argv[1]);
pmm_error("unknown command '%s' (try 'pmm help')\n", argv[1]);
return 1;
}
+53
查看文件
@@ -0,0 +1,53 @@
/* out.c - colored/structured console output implementation */
#include "out.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#define IS_TTY_FD(fd) _isatty(fd)
#else
#include <unistd.h>
#define IS_TTY_FD(fd) isatty(fd)
#endif
/* Emit a message with an optional ANSI colour and a [PMM]:[LEVEL] prefix.
* fd_stream: 1 for stdout (info/success), 2 for stderr (error/warn).
* colour is the ANSI SGR number (0 = no colour), OR "" style. We always write
* the plain "[PMM]:[LEVEL]" prefix; colour only wraps it when the tty test
* passes. */
static void pmmsg(FILE *stream, int fd, const char *level, int wrap_color, const char *fmt, va_list ap) {
char buf[4096];
vsnprintf(buf, sizeof(buf), fmt, ap);
int tty = IS_TTY_FD(fd);
if (tty && wrap_color)
fprintf(stream, "\033[%dm[PMM]:[%s]%s\033[0m\n", wrap_color, level, buf);
else
fprintf(stream, "[PMM]:[%s]%s\n", level, buf);
}
void pmm_error(const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
pmmsg(stderr, 2, "ERROR", 31, fmt, ap); /* red */
va_end(ap);
}
void pmm_success(const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
pmmsg(stdout, 1, "SUCCESS", 32, fmt, ap); /* green */
va_end(ap);
}
void pmm_info(const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
pmmsg(stdout, 1, "INFO", 2, fmt, ap); /* grey (SGR 2 = dim) */
va_end(ap);
}
void pmm_warn(const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
pmmsg(stderr, 2, "WARN", 33, fmt, ap); /* yellow */
va_end(ap);
}
+21
查看文件
@@ -0,0 +1,21 @@
/* out.h - colored/structured console output
*
* Every user-facing message goes through one of these four helpers so PMM gets
* a consistent "[PMM]:[LEVEL]" prefix and, on a real terminal, ANSI colour:
* pmm_error -> stderr, red [PMM]:[ERROR]
* pmm_success -> stdout, green [PMM]:[SUCCESS]
* pmm_info -> stdout, grey [PMM]:[INFO]
* pmm_warn -> stderr, yellow [PMM]:[WARN]
*
* Colour is emitted ONLY when the output stream is a terminal (isatty); when
* redirecting to a file/pipe the text is printed plain with no escape codes.
*/
#ifndef PMM_OUT_H
#define PMM_OUT_H
void pmm_error(const char *fmt, ...);
void pmm_success(const char *fmt, ...);
void pmm_info(const char *fmt, ...);
void pmm_warn(const char *fmt, ...);
#endif
+22 -21
查看文件
@@ -1,6 +1,7 @@
/* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */
#include "pdm.h"
#include "pmm.h"
#include "out.h"
#include "install.h"
#include "sha256.h"
#include <stdio.h>
@@ -136,13 +137,13 @@ int pdm_pack(const char *dir, const char *out) {
snprintf(cp_, sizeof(cp_), "%s/pdm-control", dir);
char *control = read_file(cp_, NULL);
if (!control) {
fprintf(stderr, "pdm: %s has no pdm-control file\n", dir);
pmm_error("%s has no pdm-control file\n", dir);
return -1;
}
char *pkg = control_get(control, "Package");
char *ver = control_get(control, "Version");
if (!pkg || !*pkg) {
fprintf(stderr, "pdm: pdm-control missing 'Package:' field\n");
pmm_error("pdm-control missing 'Package:' field\n");
free(control); return -1;
}
if (!ver || !*ver) ver = strdup("0.0.0");
@@ -163,13 +164,13 @@ int pdm_pack(const char *dir, const char *out) {
/* control.tar.gz (contains pdm-control; packed from the source dir) */
snprintf(cmd, sizeof(cmd), "tar -czf \"%s/control.tar.gz\" -C \"%s\" pdm-control", stage, dir);
if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (control) failed\n"); rmtree(stage); return -1; }
if (system(cmd) != 0) { pmm_error("tar (control) failed\n"); rmtree(stage); return -1; }
/* data.tar.gz (everything except pdm-control and the scratch dir) */
snprintf(cmd, sizeof(cmd),
"tar -czf \"%s/data.tar.gz\" -C \"%s\" --exclude pdm-control --exclude %s .",
stage, dir, stage);
if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (data) failed\n"); rmtree(stage); return -1; }
if (system(cmd) != 0) { pmm_error("tar (data) failed\n"); rmtree(stage); return -1; }
/* sha256sums of the two members */
char sums_path[1100], hex[128];
@@ -188,9 +189,9 @@ int pdm_pack(const char *dir, const char *out) {
snprintf(cmd, sizeof(cmd), "tar -cf \"%s\" -C \"%s\" control.tar.gz data.tar.gz sha256sums", out, stage);
int rc = system(cmd);
rmtree(stage);
if (rc != 0) { fprintf(stderr, "pdm: failed to write %s\n", out); return -1; }
if (rc != 0) { pmm_error("failed to write %s\n", out); return -1; }
printf("pdm: packed %s (%s %s) -> %s\n", dir, pkg, ver, out);
pmm_success("packed %s (%s %s) -> %s\n", dir, pkg, ver, out);
free(control); free(pkg);
if (strcmp(ver, "0.0.0") != 0) free(ver);
return 0;
@@ -354,7 +355,7 @@ static void flatten_bin(const char *dir) {
}
int pdm_info(const char *pdmfile) {
printf("pdm: %s\n", base_name(pdmfile));
pmm_info("%s\n", base_name(pdmfile));
/* Copy into the pm dir + chdir, then use relative names (works with both
* MSYS GNU tar and System32 bsdtar). */
char home[1024];
@@ -373,7 +374,7 @@ int pdm_info(const char *pdmfile) {
int pdm_install_file(const char *pdmfile) {
if (!ends_with(pdmfile, ".pdm")) {
fprintf(stderr, "pdm: '%s' is not a .pdm file\n", pdmfile);
pmm_error("'%s' is not a .pdm file\n", pdmfile);
return -1;
}
char home[1024], db[1024], root[1024], cmd[2600];
@@ -393,11 +394,11 @@ int pdm_install_file(const char *pdmfile) {
char tmpname[1400], tmprel[] = "_pmm_install.pdm";
snprintf(tmpname, sizeof(tmpname), "%s/_pmm_install.pdm", home);
if (copy_file(pdmfile, tmpname) != 0) {
fprintf(stderr, "pdm: cannot read %s\n", pdmfile);
pmm_error("cannot read %s\n", pdmfile);
return -1;
}
if (chdir_save(home) != 0) { remove(tmpname); return -1; }
if (system(NULL) == 0) { fprintf(stderr, "pdm: cannot run tar\n"); chdir_restore(); remove(tmpname); return -1; }
if (system(NULL) == 0) { pmm_error("cannot run tar\n"); chdir_restore(); remove(tmpname); return -1; }
const char *stage = "installed/.stage";
rmtree(stage);
@@ -406,7 +407,7 @@ int pdm_install_file(const char *pdmfile) {
/* extract members (all relative) */
snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", tmprel, stage);
if (system(cmd) != 0) {
fprintf(stderr, "pdm: not a valid .pdm archive: %s\n", pdmfile);
pmm_error("not a valid .pdm archive: %s\n", pdmfile);
chdir_restore(); remove(tmpname); return -1;
}
@@ -420,21 +421,21 @@ int pdm_install_file(const char *pdmfile) {
char mp[1200];
snprintf(mp, sizeof(mp), "installed/.stage/%s", fname);
if (pmm_sha256_file(mp, hex) != 0 || strcasecmp(hex, expect) != 0) {
fprintf(stderr, "pdm: CHECKSUM MISMATCH for %s in %s\n", fname, pdmfile);
pmm_error("CHECKSUM MISMATCH for %s in %s\n", fname, pdmfile);
fclose(sf); chdir_restore(); remove(tmpname); return -1;
}
}
fclose(sf);
printf("pdm: sha256 checksums OK\n");
pmm_success("sha256 checksums OK\n");
} else {
printf("pdm: warning: no sha256sums member, skipping integrity check\n");
pmm_warn("warning: no sha256sums member, skipping integrity check\n");
}
/* read control */
snprintf(cmd, sizeof(cmd), "tar -xzOf \"%s/control.tar.gz\" pdm-control", stage);
FILE *cf = PMM_POPEN_READ(cmd);
if (!cf) {
fprintf(stderr, "pdm: missing control.tar.gz in %s\n", pdmfile);
pmm_error("missing control.tar.gz in %s\n", pdmfile);
chdir_restore(); remove(tmpname); return -1;
}
char ctl[4096];
@@ -447,7 +448,7 @@ int pdm_install_file(const char *pdmfile) {
char *pkg = control_get(ctl, "Package");
char *ver = control_get(ctl, "Version");
if (!pkg || !*pkg) {
fprintf(stderr, "pdm: package has no Package: field\n");
pmm_error("package has no Package: field\n");
chdir_restore(); remove(tmpname); return -1;
}
@@ -455,7 +456,7 @@ int pdm_install_file(const char *pdmfile) {
const char *tgt = flat ? "." : "root";
snprintf(cmd, sizeof(cmd), "tar -xzf \"%s/data.tar.gz\" -C \"%s\"", stage, tgt);
if (system(cmd) != 0) {
fprintf(stderr, "pdm: data extraction failed\n");
pmm_error("data extraction failed\n");
chdir_restore(); remove(tmpname); return -1;
}
@@ -486,7 +487,7 @@ int pdm_install_file(const char *pdmfile) {
}
PMM_PCLOSE_READ(tf);
} else {
fprintf(stderr, "pdm: warning: could not record file list\n");
pmm_warn("warning: could not record file list\n");
}
fclose(dbf);
}
@@ -494,7 +495,7 @@ int pdm_install_file(const char *pdmfile) {
rmtree(stage);
chdir_restore();
remove(tmpname);
printf("pdm: installed %s %s into %s\n", pkg, ver ? ver : "0.0.0", root);
pmm_success("installed %s %s into %s\n", pkg, ver ? ver : "0.0.0", root);
/* register in the Windows per-user "Installed Apps" (Uninstall) hive,
* like an MSI installer would, so it shows in Settings > Installed apps */
@@ -553,7 +554,7 @@ int pdm_remove(const char *name) {
long len = 0;
char *info = read_file(ipath, &len);
if (!info) {
fprintf(stderr, "pdm: package '%s' is not installed\n", name);
pmm_error("package '%s' is not installed\n", name);
return -1;
}
char *files = strstr(info, "Files:\n");
@@ -581,7 +582,7 @@ int pdm_remove(const char *name) {
}
remove(ipath);
free(info);
printf("pdm: removed %s (%d files)\n", name, n);
pmm_success("removed %s (%d files)\n", name, n);
pmm_reg_uninstall_clear(name); /* drop it from Installed Apps */
return 0;
}
+10 -9
查看文件
@@ -1,5 +1,6 @@
/* pmm.c - platform & config-dir helpers */
#include "pmm.h"
#include "out.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -129,7 +130,7 @@ void pmm_set_install_drive(const char *letter) {
mkdir_p(dir);
FILE *f = fopen(state, "w");
if (f) { fprintf(f, "%c", c); fclose(f); }
printf("pmm: install drive set to %c:\n", c);
pmm_success("install drive set to %c:\n", c);
}
void pmm_set_install_path(const char *path) {
@@ -139,7 +140,7 @@ void pmm_set_install_path(const char *path) {
size_t l = strlen(g_base_path);
while (l > 1 && (g_base_path[l-1] == '/' || g_base_path[l-1] == '\\')) g_base_path[--l] = '\0';
mkdir_p(g_base_path);
printf("pmm: install path set to %s\n", g_base_path);
pmm_success("install path set to %s\n", g_base_path);
}
int pmm_flat_mode(void) { return g_base_path[0] ? 1 : 0; }
@@ -407,9 +408,9 @@ void pmm_add_to_path(void) {
if (room <= 0) break; /* no space left: stop */
snprintf(next + l, room + 1, "%s", list[i]);
added++;
printf("pmm: adding to PATH: %s\n", list[i]);
pmm_info("adding to PATH: %s\n", list[i]);
}
if (!added) { printf("pmm: PATH already contains PMM dirs\n"); return; }
if (!added) { pmm_info("PATH already contains PMM dirs\n"); return; }
#ifdef _WIN32
{
@@ -417,7 +418,7 @@ void pmm_add_to_path(void) {
/* drive letters and ';' are fine for setx; quote the value */
snprintf(cmd, sizeof(cmd), "setx Path \"%s\" >nul 2>&1", next);
system(cmd);
printf("pmm: PATH updated for new shells (reopen a terminal or run: setx) (user-level)\n");
pmm_success("PATH updated for new shells (reopen a terminal or run: setx) (user-level)\n");
}
#else
{
@@ -432,7 +433,7 @@ void pmm_add_to_path(void) {
fclose(f);
}
(void)she;
printf("pmm: updated %s (reload your shell)\n", rcp);
pmm_success("updated %s (reload your shell)\n", rcp);
}
#endif
}
@@ -513,9 +514,9 @@ int pmm_reg_uninstall(const char *pkg, const char *ver, const char *pub,
int rc = system(cmd);
remove(ps1);
if (rc != 0)
fprintf(stderr, "pmm: warning: could not register '%s' in Installed Apps\n", pkg);
pmm_warn("warning: could not register '%s' in Installed Apps\n", pkg);
else
printf("pmm: registered '%s' %s in Installed Apps (uninstall via pmm remove %s)\n",
pmm_success("registered '%s' %s in Installed Apps (uninstall via pmm remove %s)\n",
pkg, ver ? ver : "", pkg);
return rc == 0 ? 0 : -1;
#endif
@@ -531,6 +532,6 @@ void pmm_reg_uninstall_clear(const char *pkg) {
"%%SystemRoot%%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -Command \"Remove-Item -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s' -Recurse -Force -ErrorAction SilentlyContinue\" 2>nul",
pkg);
system(cmd);
printf("pmm: removed '%s' from Installed Apps\n", pkg);
pmm_success("removed '%s' from Installed Apps\n", pkg);
#endif
}