Skip to content

Commit 7c247fc

Browse files
committed
fix cache handling for upgrade, cleanup docs and version output
1 parent 4d78ca7 commit 7c247fc

7 files changed

Lines changed: 318 additions & 119 deletions

File tree

README.md

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,6 @@ abxpkg uninstall yt-dlp
101101
abxpkg load yt-dlp
102102
```
103103

104-
Hidden aliases are also available:
105-
106-
```bash
107-
abxpkg add yt-dlp # alias for install
108-
abxpkg upgrade yt-dlp # alias for update
109-
abxpkg remove yt-dlp # alias for uninstall
110-
```
111-
112104
`abxpkg --version` and `abxpkg version` stream the package version first, then a host/env summary line, then one section per selected provider showing its current resolved runtime state (`INSTALLER_BINARY`, `PATH`, `ENV`, `install_root`, `bin_dir`, and any active cached dependency / installed binaries).
113105

114106
`abxpkg version <binary>` is a thin alias for `abxpkg load <binary>`.
@@ -206,7 +198,7 @@ abx --min-version=2024.1.1 --min-release-age=0 yt-dlp --help
206198
| `--dry-run[=BOOL]` | `bool` | Show installer commands without executing them. Bare `--dry-run` = `True`. |
207199
| `--debug[=BOOL]` | `bool` | Emit DEBUG logs to `stderr`. Bare `--debug` = `True`. Defaults to `ABXPKG_DEBUG` or `False`. |
208200

209-
Every value-taking flag also accepts the literal string `None` / `null` / `nil` / `""` to reset to the provider's built-in default (or its env-var-backed default). The precedence is: explicit per-subcommand flag > group-level flag > environment variable > built-in default.
201+
Every value-taking flag also accepts the literal string `None` / `null` / `""` to reset to the provider's default resolution path. For `postinstall_scripts` / `min_release_age`, that means the action-specific effective default for that provider (`False` / `7` on supporting providers, `True` / `0` otherwise). The precedence is: explicit per-subcommand flag > group-level flag > environment variable > built-in default.
210202

211203
#### Select specific providers / re-order provider precedence
212204

@@ -609,8 +601,8 @@ All abxpkg env vars are read once at import time and only apply when set. Explic
609601
| `ABXPKG_DEBUG` | `0` | Enables DEBUG-level CLI logging on `stderr` for `abxpkg` / `abx`. The matching CLI flag is `--debug`. Default CLI logging level is `INFO`. |
610602
| `ABXPKG_INSTALL_TIMEOUT` | `120` | Seconds to wait for `install()` / `update()` / `uninstall()` handler subprocesses. |
611603
| `ABXPKG_VERSION_TIMEOUT` | `10` | Seconds to wait for version / metadata probes (`--version`, `npm show`, `pip show`, etc.). |
612-
| `ABXPKG_POSTINSTALL_SCRIPTS` | unset | Hydrates the provider-level default for the `postinstall_scripts` kwarg on every provider that supports it (`pip`, `uv`, `npm`, `pnpm`, `yarn`, `bun`, `deno`, `brew`, `chromewebstore`, `puppeteer`). |
613-
| `ABXPKG_MIN_RELEASE_AGE` | `7` | Hydrates the provider-level default (in days) for the `min_release_age` kwarg on every provider that supports it (`pip`, `uv`, `npm`, `pnpm`, `yarn`, `bun`, `deno`). |
604+
| `ABXPKG_POSTINSTALL_SCRIPTS` | unset | Hydrates the provider-level default for the `postinstall_scripts` kwarg on every provider that supports it (`pip`, `uv`, `npm`, `pnpm`, `yarn`, `bun`, `deno`, `brew`, `chromewebstore`, `puppeteer`). When left unset, action execution resolves to the provider/action default (`False` on supporting providers, `True` otherwise). |
605+
| `ABXPKG_MIN_RELEASE_AGE` | `7` | Hydrates the provider-level default (in days) for the `min_release_age` kwarg on every provider that supports it (`pip`, `uv`, `npm`, `pnpm`, `yarn`, `bun`, `deno`). When left unset, action execution resolves to the provider/action default (`7` on supporting providers, `0` otherwise). |
614606
| `ABXPKG_BINPROVIDERS` | shared default order | Comma-separated list of provider names to enable (and their order) for the `abxpkg` CLI. By default this uses `DEFAULT_PROVIDER_NAMES` from `abxpkg.__init__` (which excludes `ansible` / `pyinfra`, and also excludes `apt` on macOS). |
615607

616608
**Install-root controls** (one global default + one per-provider override):
@@ -680,7 +672,7 @@ dry_run = False # or ABXPKG_DRY_RUN=1 / DRY_RUN=1
680672
- `no_cache`: use `--no-cache` / `ABXPKG_NO_CACHE=1` on the CLI, or pass `no_cache=True` directly to `load()` / `install()` / `update()` / `uninstall()`. For `install()`, this skips the initial `load()` check and forces a fresh install path.
681673
- `install_timeout`: shared provider-level timeout used by `install()`, `update()`, and `uninstall()` handler execution paths. Can also be set with `ABXPKG_INSTALL_TIMEOUT`.
682674
- `version_timeout`: shared provider-level timeout used by version / metadata probes such as `--version`, `npm show`, `npm list`, `pip show`, `go version -m`, and brew lookups. Can also be set with `ABXPKG_VERSION_TIMEOUT`.
683-
- `postinstall_scripts` and `min_release_age` are standard provider/binary/action kwargs, but only supporting providers hydrate default values from `ABXPKG_POSTINSTALL_SCRIPTS` and `ABXPKG_MIN_RELEASE_AGE`.
675+
- `postinstall_scripts` and `min_release_age` are standard provider/binary/action kwargs. Supporting providers hydrate defaults from `ABXPKG_POSTINSTALL_SCRIPTS` and `ABXPKG_MIN_RELEASE_AGE`; when those remain unset/`None`, install/update/uninstall resolve them to effective action defaults (`False` / `7` on supporting providers, `True` / `0` otherwise).
684676
- Providers that do not support one of those controls leave the provider default as `None`. If you pass an explicit unsupported value during `install()` / `update()`, it is logged as a warning and ignored.
685677
- Precedence is: explicit action args > `Binary(...)` defaults > provider defaults.
686678

abxpkg/binprovider.py

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,7 @@ def has_cached_binary(self, bin_name: BinName) -> bool:
13271327
cached_provider_name = cache_value.get("provider_name")
13281328
cached_bin_name = cache_value.get("bin_name")
13291329
cached_abspath = cache_value.get("abspath")
1330+
cache_kind = cache_value.get("cache_kind")
13301331
if not isinstance(cached_provider_name, str) or not isinstance(
13311332
cached_bin_name,
13321333
str,
@@ -1343,6 +1344,14 @@ def has_cached_binary(self, bin_name: BinName) -> bool:
13431344
or not isinstance(cached_abspath, str)
13441345
):
13451346
continue
1347+
if not isinstance(cache_kind, str):
1348+
cache_kind = (
1349+
"dependency"
1350+
if str(cached_bin_name) == str(self.INSTALLER_BIN)
1351+
else "binary"
1352+
)
1353+
if cache_kind != "binary":
1354+
continue
13461355
cached_path = Path(cached_abspath)
13471356
if not (cached_path.exists() or cached_path.is_symlink()):
13481357
cache.pop(cache_key, None)
@@ -1353,6 +1362,138 @@ def has_cached_binary(self, bin_name: BinName) -> bool:
13531362
save_derived_cache(derived_env_path, cache)
13541363
return has_valid_cache
13551364

1365+
@log_method_call(include_result=True)
1366+
def depends_on_binaries(self) -> list[ShallowBinary]:
1367+
derived_env_path = self.derived_env_path
1368+
if derived_env_path is None or not derived_env_path.is_file():
1369+
return []
1370+
cache = load_derived_cache(derived_env_path)
1371+
dependencies: list[ShallowBinary] = []
1372+
seen: set[tuple[str, str, str]] = set()
1373+
for cache_key, cache_value in sorted(cache.items()):
1374+
if not isinstance(cache_value, dict):
1375+
continue
1376+
cached_provider_name = cache_value.get("provider_name")
1377+
cached_bin_name = cache_value.get("bin_name")
1378+
cached_abspath = cache_value.get("abspath")
1379+
cache_kind = cache_value.get("cache_kind")
1380+
if (
1381+
not isinstance(cached_provider_name, str)
1382+
or not isinstance(cached_bin_name, str)
1383+
or not isinstance(cached_abspath, str)
1384+
):
1385+
try:
1386+
cached_provider_name, cached_bin_name, cached_abspath = json.loads(
1387+
cache_key,
1388+
)
1389+
except Exception:
1390+
continue
1391+
if cached_provider_name != self.name or not isinstance(cached_abspath, str):
1392+
continue
1393+
if not isinstance(cache_kind, str):
1394+
cache_kind = (
1395+
"dependency"
1396+
if str(cached_bin_name) == str(self.INSTALLER_BIN)
1397+
else "binary"
1398+
)
1399+
if cache_kind != "dependency":
1400+
continue
1401+
loaded = self.load_cached_binary(cached_bin_name, Path(cached_abspath))
1402+
if loaded is None or loaded.loaded_abspath is None:
1403+
continue
1404+
resolved_provider = loaded.loaded_binprovider or self
1405+
dedupe_key = (
1406+
loaded.name,
1407+
str(loaded.loaded_abspath),
1408+
resolved_provider.name,
1409+
)
1410+
if dedupe_key in seen:
1411+
continue
1412+
seen.add(dedupe_key)
1413+
dependencies.append(
1414+
ShallowBinary.model_validate(
1415+
{
1416+
"name": loaded.name,
1417+
"description": loaded.description,
1418+
"binprovider": resolved_provider,
1419+
"abspath": loaded.loaded_abspath,
1420+
"version": loaded.loaded_version,
1421+
"sha256": loaded.loaded_sha256,
1422+
"mtime": loaded.loaded_mtime,
1423+
"euid": loaded.loaded_euid,
1424+
"binproviders": [resolved_provider],
1425+
"overrides": loaded.overrides,
1426+
},
1427+
),
1428+
)
1429+
return dependencies
1430+
1431+
@log_method_call(include_result=True)
1432+
def installed_binaries(self) -> list[ShallowBinary]:
1433+
derived_env_path = self.derived_env_path
1434+
if derived_env_path is None or not derived_env_path.is_file():
1435+
return []
1436+
cache = load_derived_cache(derived_env_path)
1437+
binaries: list[ShallowBinary] = []
1438+
seen: set[tuple[str, str, str]] = set()
1439+
for cache_key, cache_value in sorted(cache.items()):
1440+
if not isinstance(cache_value, dict):
1441+
continue
1442+
cached_provider_name = cache_value.get("provider_name")
1443+
cached_bin_name = cache_value.get("bin_name")
1444+
cached_abspath = cache_value.get("abspath")
1445+
cache_kind = cache_value.get("cache_kind")
1446+
if (
1447+
not isinstance(cached_provider_name, str)
1448+
or not isinstance(cached_bin_name, str)
1449+
or not isinstance(cached_abspath, str)
1450+
):
1451+
try:
1452+
cached_provider_name, cached_bin_name, cached_abspath = json.loads(
1453+
cache_key,
1454+
)
1455+
except Exception:
1456+
continue
1457+
if cached_provider_name != self.name or not isinstance(cached_abspath, str):
1458+
continue
1459+
if not isinstance(cache_kind, str):
1460+
cache_kind = (
1461+
"dependency"
1462+
if str(cached_bin_name) == str(self.INSTALLER_BIN)
1463+
else "binary"
1464+
)
1465+
if cache_kind != "binary":
1466+
continue
1467+
loaded = self.load_cached_binary(cached_bin_name, Path(cached_abspath))
1468+
if loaded is None or loaded.loaded_abspath is None:
1469+
continue
1470+
resolved_provider = loaded.loaded_binprovider or self
1471+
dedupe_key = (
1472+
loaded.name,
1473+
str(loaded.loaded_abspath),
1474+
resolved_provider.name,
1475+
)
1476+
if dedupe_key in seen:
1477+
continue
1478+
seen.add(dedupe_key)
1479+
binaries.append(
1480+
ShallowBinary.model_validate(
1481+
{
1482+
"name": loaded.name,
1483+
"description": loaded.description,
1484+
"binprovider": resolved_provider,
1485+
"abspath": loaded.loaded_abspath,
1486+
"version": loaded.loaded_version,
1487+
"sha256": loaded.loaded_sha256,
1488+
"mtime": loaded.loaded_mtime,
1489+
"euid": loaded.loaded_euid,
1490+
"binproviders": [resolved_provider],
1491+
"overrides": loaded.overrides,
1492+
},
1493+
),
1494+
)
1495+
return binaries
1496+
13561497
def setup_PATH(self, no_cache: bool = False) -> None:
13571498
"""Populate runtime PATH lazily.
13581499
@@ -2124,15 +2265,7 @@ def update(
21242265
getattr(getattr(update_handler, "__func__", update_handler), "__name__", "")
21252266
== "update_noop"
21262267
):
2127-
result = self.load(bin_name=bin_name, quiet=quiet, no_cache=no_cache)
2128-
if result is not None:
2129-
self._assert_min_version_satisfied(
2130-
bin_name=bin_name,
2131-
action="update",
2132-
loaded_version=result.loaded_version,
2133-
min_version=min_version,
2134-
)
2135-
return result
2268+
return None
21362269

21372270
self.setup(
21382271
postinstall_scripts=postinstall_scripts,
@@ -2184,7 +2317,7 @@ def update(
21842317

21852318
self.invalidate_cache(bin_name)
21862319

2187-
result = self.load(bin_name, quiet=True, no_cache=True)
2320+
result = self.load(bin_name, quiet=True, no_cache=no_cache)
21882321
if not quiet:
21892322
assert result is not None, (
21902323
f"❌ {self.__class__.__name__} Unable to find version for {bin_name} after updating. PATH={self.PATH} LOG={update_log}"
@@ -2612,6 +2745,7 @@ def has_cached_binary(self, bin_name: BinName) -> bool:
26122745
cached_provider_name = cache_value.get("provider_name")
26132746
cached_bin_name = cache_value.get("bin_name")
26142747
cached_abspath = cache_value.get("abspath")
2748+
cache_kind = cache_value.get("cache_kind")
26152749
if (
26162750
not isinstance(cached_provider_name, str)
26172751
or not isinstance(cached_bin_name, str)
@@ -2626,6 +2760,14 @@ def has_cached_binary(self, bin_name: BinName) -> bool:
26262760

26272761
if cached_provider_name != self.name or cached_bin_name != str(bin_name):
26282762
continue
2763+
if not isinstance(cache_kind, str):
2764+
cache_kind = (
2765+
"dependency"
2766+
if str(cached_bin_name) == str(self.INSTALLER_BIN)
2767+
else "binary"
2768+
)
2769+
if cache_kind != "binary":
2770+
continue
26292771

26302772
if self._is_managed_by_other_provider(Path(cached_abspath)):
26312773
cache.pop(cache_key, None)

0 commit comments

Comments
 (0)