Skip to content

Commit 836123b

Browse files
committed
Puppeteer/Playwright: drop cache_dir entirely, inline install_root/cache
``cache_dir`` was a one-line computed helper (``install_root/cache`` when managed, ``None`` otherwise) that only existed as a convenience alias — every callsite already branched on ``is not None``. Remove the computed field and inline ``install_root / "cache"`` at each use: - ``ENV`` now returns ``{}`` when ``install_root`` is None and ``{"PUPPETEER_CACHE_DIR": "<install_root>/cache"}`` / ``{"PLAYWRIGHT_BROWSERS_PATH": "<install_root>/cache"}`` otherwise. - ``setup`` creates ``<install_root>/cache`` next to the existing ``install_root.mkdir``. - ``_normalize_install_args`` / ``_list_installed_browsers`` pass ``--path=<install_root>/cache`` only when managed. - ``_cleanup_partial_browser_cache`` takes an early-return when ``install_root`` is None and uses a local ``cache_dir`` var for the glob/rmtree loop. - Playwright ``build_exec_env`` sudo wrapper and ``default_abspath_handler`` scope check both use a local ``cache_dir`` var under ``install_root is not None``. - Uninstall handlers already resolved the browser dir via ``load()`` in the previous commit, so they don't touch cache_dir at all. Net result: both providers' public field surface is just ``install_root`` + ``bin_dir``, matching the "only install_root/bin_dir" direction.
1 parent 10c3759 commit 836123b

3 files changed

Lines changed: 53 additions & 80 deletions

File tree

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,11 +1074,10 @@ INSTALLER_BIN = "puppeteer-browsers"
10741074
PATH = ""
10751075
install_root = $ABXPKG_PUPPETEER_ROOT or $ABXPKG_LIB_DIR/puppeteer
10761076
bin_dir = <install_root>/bin
1077-
cache_dir = <install_root>/cache # computed; None when install_root is unset
10781077
```
10791078

10801079
- Install root: set `install_root` for the root dir and `bin_dir` for symlinked executables. Leave it unset for ambient/global mode, where cache ownership stays with the host and `INSTALLER_BINARY` must already be resolvable from the ambient provider set.
1081-
- Cache dir: `cache_dir` is a computed helper property. When `install_root` is pinned it's `<install_root>/cache` and abxpkg owns it — exported as `PUPPETEER_CACHE_DIR` to every subprocess, used for `--path=` on `puppeteer-browsers install` / `list`, and rmtree'd per-browser on `uninstall()`. When `install_root` is unset, `cache_dir` is `None`: the provider is in pure passthrough mode. The caller's ambient `$PUPPETEER_CACHE_DIR` (or the CLI's `~/.cache/puppeteer` default) flows through to subprocesses unchanged, `load()` trusts whatever path `puppeteer-browsers list` reports, and `uninstall()` is a no-op — we don't touch the user's own cache.
1080+
- Browser cache: when `install_root` is pinned, abxpkg manages `<install_root>/cache` end-to-end — it's exported as `PUPPETEER_CACHE_DIR` to every subprocess, used for `--path=` on `puppeteer-browsers install` / `list`, and `uninstall()` resolves the real browser directory via `load()` then rmtrees it. When `install_root` is unset the provider is in pure passthrough mode: the caller's ambient `$PUPPETEER_CACHE_DIR` (or the CLI's `~/.cache/puppeteer` default) flows through to subprocesses unchanged, `load()` trusts whatever path `puppeteer-browsers list` reports, and `uninstall()` still rmtrees the real browser directory returned by `load()` — leaving any unrelated browsers in the shared cache alone.
10821081
- Auto-switching: bootstraps `@puppeteer/browsers` through `NpmProvider` and then uses that CLI for browser installs.
10831082
- `dry_run`: shared behavior.
10841083
- Security: `min_release_age` is unsupported for browser installs and is ignored with a warning if explicitly requested. `postinstall_scripts=False` is supported for the underlying npm bootstrap path, and `ABXPKG_POSTINSTALL_SCRIPTS` hydrates the provider default here.
@@ -1097,12 +1096,11 @@ INSTALLER_BIN = "playwright"
10971096
PATH = ""
10981097
install_root = None # abxpkg-managed root dir for bin_dir / nested npm prefix
10991098
bin_dir = <install_root>/bin # symlink dir for resolved browsers
1100-
cache_dir = <install_root>/cache # computed; None when install_root is unset
11011099
euid = 0 # routes exec() through sudo-first-then-fallback
11021100
```
11031101

11041102
- Install root: set `install_root` to pin the abxpkg-managed root dir (where `bin_dir` symlinks and the nested npm prefix live). Leave it unset to let playwright use its own OS-default browsers path (`~/.cache/ms-playwright` on Linux etc.) — in that case abxpkg maintains no symlink dir or npm prefix at all, the `playwright` npm CLI bootstraps against the host's npm default, and `load()` returns the resolved `executablePath()` directly. `bin_dir` overrides the symlink directory when `install_root` is pinned.
1105-
- Cache dir: `cache_dir` is a computed helper property. When `install_root` is pinned it's `<install_root>/cache` and abxpkg owns it — exported as `PLAYWRIGHT_BROWSERS_PATH` to every subprocess (including the `env KEY=VAL -- ...` wrapper used when we go through sudo), used to scope `executablePath()` hits on `load()`, and walked for per-browser rmtree on `uninstall()`. When `install_root` is unset, `cache_dir` is `None`: the provider is in pure passthrough mode. The caller's ambient `$PLAYWRIGHT_BROWSERS_PATH` (or playwright's `~/.cache/ms-playwright` default on Linux) flows through to subprocesses unchanged, `load()` trusts whatever path `executablePath()` reports, and `uninstall()` is a no-op — we don't touch the user's own cache.
1103+
- Browser cache: when `install_root` is pinned, abxpkg manages `<install_root>/cache` end-to-end — exported as `PLAYWRIGHT_BROWSERS_PATH` to every subprocess (including the `env KEY=VAL -- ...` wrapper used when we go through sudo), used to scope `executablePath()` hits on `load()`, and `uninstall()` resolves the real browser directory via `load()` then rmtrees it. When `install_root` is unset the provider is in pure passthrough mode: the caller's ambient `$PLAYWRIGHT_BROWSERS_PATH` (or playwright's `~/.cache/ms-playwright` default on Linux) flows through to subprocesses unchanged, `load()` trusts whatever path `executablePath()` reports, and `uninstall()` still rmtrees the real browser directory returned by `load()`.
11061104
- Auto-switching: bootstraps the `playwright` npm package through `NpmProvider`, then runs `playwright install --with-deps <install_args>` against it. Resolves each installed browser's real executable via the `playwright-core` Node.js API (`chromium.executablePath()` etc.) and writes a symlink into `bin_dir` when one is configured.
11071105
- `dry_run`: shared behavior — the install handler short-circuits to a placeholder without touching the host.
11081106
- Privilege handling: `--with-deps` installs system packages and requires root on Linux. ``euid`` defaults to ``0``, which routes every ``exec()`` call through the base ``BinProvider.exec`` sudo-first-then-fallback path — it tries ``sudo -n -- playwright install --with-deps ...`` first on non-root hosts, falls back to running the command directly if sudo fails or isn't available, and merges both stderr outputs into the final error if both attempts fail.

abxpkg/binprovider_playwright.py

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -77,29 +77,16 @@ class PlaywrightProvider(BinProvider):
7777
# run as the normal user by default.
7878
euid: int | None = 0 if platform.system().lower() == "linux" else None
7979

80-
@computed_field
81-
@property
82-
def cache_dir(self) -> Path | None:
83-
"""``<install_root>/cache`` when managed, else ``None``.
84-
85-
Internal helper for the install/uninstall/load sites. When
86-
``install_root`` is unset we stay out of the way: the ambient
87-
``PLAYWRIGHT_BROWSERS_PATH`` (or playwright's
88-
``~/.cache/ms-playwright`` default) passes through to
89-
subprocesses untouched, ``default_abspath_handler`` trusts
90-
whatever path ``executablePath()`` reports, and
91-
``default_uninstall_handler`` skips rmtree of the user's cache.
92-
"""
93-
if self.install_root is None:
94-
return None
95-
return self.install_root / "cache"
96-
9780
@computed_field
9881
@property
9982
def ENV(self) -> "dict[str, str]":
100-
if not self.cache_dir:
83+
# In managed mode we pin ``PLAYWRIGHT_BROWSERS_PATH`` to
84+
# ``<install_root>/cache``. In unmanaged mode we export nothing
85+
# and the ambient env (or playwright's own
86+
# ``~/.cache/ms-playwright`` default) passes through untouched.
87+
if self.install_root is None:
10188
return {}
102-
return {"PLAYWRIGHT_BROWSERS_PATH": str(self.cache_dir)}
89+
return {"PLAYWRIGHT_BROWSERS_PATH": str(self.install_root / "cache")}
10390

10491
def supports_min_release_age(self, action, no_cache: bool = False) -> bool:
10592
return False
@@ -308,8 +295,8 @@ def exec(
308295
# contain our bin_dir.
309296
env = self.build_exec_env(base_env=(kwargs.pop("env", None) or os.environ))
310297
env_assignments: list[str] = []
311-
cache_dir = self.cache_dir
312-
if cache_dir is not None:
298+
if self.install_root is not None:
299+
cache_dir = self.install_root / "cache"
313300
env["PLAYWRIGHT_BROWSERS_PATH"] = str(cache_dir)
314301
env_assignments.append(
315302
f"PLAYWRIGHT_BROWSERS_PATH={cache_dir}",
@@ -587,14 +574,13 @@ def default_abspath_handler(
587574
)
588575
if not resolved:
589576
return None
590-
# When ``cache_dir`` is pinned (either explicitly, via
591-
# ``PLAYWRIGHT_BROWSERS_PATH``, or derived from ``install_root``),
592-
# an ``executablePath()`` hit that points outside that tree
593-
# (e.g. an ambient system install) should not satisfy ``load()``
594-
# — otherwise an unrelated host-wide playwright install would
595-
# silently hijack resolution.
596-
if self.cache_dir is not None:
597-
cache_real = self.cache_dir.resolve(strict=False)
577+
# When ``install_root`` is pinned, an ``executablePath()`` hit
578+
# that points outside our managed cache tree (e.g. an ambient
579+
# system install) should not satisfy ``load()`` — otherwise an
580+
# unrelated host-wide playwright install would silently hijack
581+
# resolution.
582+
if self.install_root is not None:
583+
cache_real = (self.install_root / "cache").resolve(strict=False)
598584
if cache_real not in resolved.resolve(strict=False).parents:
599585
return None
600586
if self.bin_dir is None:

abxpkg/binprovider_puppeteer.py

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -67,27 +67,16 @@ class PuppeteerProvider(BinProvider):
6767
# browser launch shims under ``<install_root>/bin``; global mode leaves it unset.
6868
bin_dir: Path | None = None
6969

70-
@computed_field
71-
@property
72-
def cache_dir(self) -> Path | None:
73-
"""``<install_root>/cache`` when managed, else ``None``.
74-
75-
Internal helper for the install/uninstall/load sites. When
76-
``install_root`` is unset we stay out of the way: the ambient
77-
``PUPPETEER_CACHE_DIR`` (or the CLI's ``~/.cache/puppeteer``
78-
default) passes through to subprocesses untouched, and we
79-
refuse to rmtree anything in the user's cache on ``uninstall``.
80-
"""
81-
if self.install_root is None:
82-
return None
83-
return self.install_root / "cache"
84-
8570
@computed_field
8671
@property
8772
def ENV(self) -> "dict[str, str]":
88-
if self.cache_dir is None:
73+
# In managed mode we pin ``PUPPETEER_CACHE_DIR`` to
74+
# ``<install_root>/cache``. In unmanaged mode we export nothing
75+
# and the ambient env (or puppeteer-browsers' own
76+
# ``~/.cache/puppeteer`` default) passes through untouched.
77+
if self.install_root is None:
8978
return {}
90-
return {"PUPPETEER_CACHE_DIR": str(self.cache_dir)}
79+
return {"PUPPETEER_CACHE_DIR": str(self.install_root / "cache")}
9180

9281
def supports_postinstall_disable(self, action, no_cache: bool = False) -> bool:
9382
return action in ("install", "update")
@@ -210,7 +199,9 @@ def setup(
210199
owner_paths=(
211200
self.install_root,
212201
self.bin_dir,
213-
self.cache_dir,
202+
self.install_root / "cache"
203+
if self.install_root is not None
204+
else None,
214205
self.install_root / "npm"
215206
if self.install_root is not None
216207
else None,
@@ -246,10 +237,9 @@ def setup(
246237

247238
if self.install_root is not None:
248239
self.install_root.mkdir(parents=True, exist_ok=True)
240+
(self.install_root / "cache").mkdir(parents=True, exist_ok=True)
249241
if self.bin_dir is not None:
250242
self.bin_dir.mkdir(parents=True, exist_ok=True)
251-
if self.cache_dir is not None:
252-
self.cache_dir.mkdir(parents=True, exist_ok=True)
253243

254244
cli_binary = self._cli_binary(
255245
postinstall_scripts=postinstall_scripts,
@@ -300,8 +290,8 @@ def _normalize_install_args(self, install_args: Iterable[str]) -> list[str]:
300290
if arg_str.startswith("--path="):
301291
continue
302292
normalized.append(arg_str)
303-
if self.cache_dir is not None:
304-
normalized.append(f"--path={self.cache_dir}")
293+
if self.install_root is not None:
294+
normalized.append(f"--path={self.install_root / 'cache'}")
305295
return normalized
306296

307297
def _list_installed_browsers(
@@ -315,8 +305,8 @@ def _list_installed_browsers(
315305
if not installer_bin:
316306
return []
317307
cmd = ["list"]
318-
if self.cache_dir is not None:
319-
cmd.append(f"--path={self.cache_dir}")
308+
if self.install_root is not None:
309+
cmd.append(f"--path={self.install_root / 'cache'}")
320310
proc = self.exec(
321311
bin_name=installer_bin,
322312
cmd=cmd,
@@ -448,10 +438,11 @@ def _cleanup_partial_browser_cache(
448438
install_output: str,
449439
browser_name: str,
450440
) -> bool:
451-
if self.cache_dir is None:
441+
if self.install_root is None:
452442
return False
443+
cache_dir = self.install_root / "cache"
453444
targets: set[Path] = set()
454-
browser_cache_dir = self.cache_dir / browser_name
445+
browser_cache_dir = cache_dir / browser_name
455446

456447
missing_dir_match = re.search(
457448
r"browser folder \(([^)]+)\) exists but the executable",
@@ -473,7 +464,7 @@ def _cleanup_partial_browser_cache(
473464
targets.update(browser_cache_dir.glob(f"*{build_id}*"))
474465

475466
removed_any = False
476-
resolved_cache = self.cache_dir.resolve(strict=False)
467+
resolved_cache = cache_dir.resolve(strict=False)
477468
for target in targets:
478469
resolved_target = target.resolve(strict=False)
479470
if not (
@@ -551,27 +542,25 @@ def _run_install_with_sudo(
551542
cwd=self.install_root or ".",
552543
timeout=self.install_timeout,
553544
)
554-
if (
555-
proc.returncode == 0
556-
and self.cache_dir is not None
557-
and self.cache_dir.exists()
558-
):
559-
uid = os.getuid()
560-
gid = os.getgid()
561-
chown_proc = self.exec(
562-
bin_name=sudo_binary.loaded_abspath,
563-
cmd=["chown", "-R", f"{uid}:{gid}", str(self.cache_dir)],
564-
cwd=self.install_root or ".",
565-
timeout=30,
566-
quiet=True,
567-
)
568-
if chown_proc.returncode != 0:
569-
log_subprocess_output(
570-
logger,
571-
f"{self.__class__.__name__} sudo chown",
572-
chown_proc.stdout,
573-
chown_proc.stderr,
545+
if proc.returncode == 0 and self.install_root is not None:
546+
cache_dir = self.install_root / "cache"
547+
if cache_dir.exists():
548+
uid = os.getuid()
549+
gid = os.getgid()
550+
chown_proc = self.exec(
551+
bin_name=sudo_binary.loaded_abspath,
552+
cmd=["chown", "-R", f"{uid}:{gid}", str(cache_dir)],
553+
cwd=self.install_root or ".",
554+
timeout=30,
555+
quiet=True,
574556
)
557+
if chown_proc.returncode != 0:
558+
log_subprocess_output(
559+
logger,
560+
f"{self.__class__.__name__} sudo chown",
561+
chown_proc.stdout,
562+
chown_proc.stderr,
563+
)
575564
return proc
576565

577566
@remap_kwargs({"packages": "install_args"})

0 commit comments

Comments
 (0)