Skip to content

Commit 6f614e9

Browse files
authored
Merge pull request #223 from plotly/andrew/ldd-check
Andrew/ldd check
2 parents 031d0c8 + b63bf47 commit 6f614e9

42 files changed

Lines changed: 147 additions & 38 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish_testpypi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
timeout-minutes: 8
5151

5252
- name: Test (Debug)
53-
if: runner.debug
53+
if: ${{ runner.debug && matrix.os != 'ubuntu-latest' }}
5454
run: uv run --no-sync poe debug-test
5555
timeout-minutes: 20
5656

.github/workflows/test.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ jobs:
2222
if: ${{ matrix.os == 'ubuntu-latest' }}
2323
run: sudo apt-get update && sudo apt-get install xvfb
2424
timeout-minutes: 1
25-
2625
- name: Install choreographer
2726
run: uv sync --no-sources --all-extras
2827
- name: Install google-chrome-for-testing
@@ -42,7 +41,7 @@ jobs:
4241
timeout-minutes: 7
4342

4443
- name: Test (Debug)
45-
if: runner.debug
44+
if: ${{ runner.debug && matrix.os != 'ubuntu-latest' }}
4645
run: uv run --no-sources poe debug-test
4746
timeout-minutes: 20
4847

CHANGELOG.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v1.0.6
2+
- Package in chromium deps and use them if ldd shows they're needed
3+
- Add env var LDD_FAIL and flag --ldd_fail to always fail if deps needed
4+
- Add env var FORCED_PACKAGED_DEPS and flag --forced-packaged-deps to do as read
5+
- Fix some API bugs in choreo_diagnose
16
v1.0.5
27
- Add Browser.is_isolated() returning if /tmp is sandboxed
38
v1.0.4

choreographer/browser_async.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,6 @@ def __init__(
101101
self._channel = channel_cls()
102102
self._broker = Broker(self, self._channel)
103103
self._browser_impl = browser_cls(self._channel, path, **kwargs)
104-
if hasattr(browser_cls, "logger_parser"):
105-
parser = browser_cls.logger_parser
106-
else:
107-
parser = None
108-
self._logger_pipe, _ = logistro.getPipeLogger(
109-
"browser_proc",
110-
parser=parser,
111-
)
112104

113105
def is_isolated(self) -> bool:
114106
"""Return if process is isolated."""
@@ -119,13 +111,23 @@ async def open(self) -> None:
119111
_logger.info("Opening browser.")
120112
if await self._is_open():
121113
raise RuntimeError("Can't re-open the browser")
122-
cli = self._browser_impl.get_cli()
123-
stderr = self._logger_pipe
124-
env = self._browser_impl.get_env()
125-
args = self._browser_impl.get_popen_args()
126114

127115
# asyncio's equiv doesn't work in all situations
116+
if hasattr(self._browser_impl, "logger_parser"):
117+
parser = self._browser_impl.logger_parser
118+
else:
119+
parser = None
120+
self._logger_pipe, _ = logistro.getPipeLogger(
121+
"browser_proc",
122+
parser=parser,
123+
)
124+
128125
def run() -> subprocess.Popen[bytes]:
126+
self._browser_impl.pre_open()
127+
cli = self._browser_impl.get_cli()
128+
stderr = self._logger_pipe
129+
env = self._browser_impl.get_env()
130+
args = self._browser_impl.get_popen_args()
129131
return subprocess.Popen( # noqa: S603
130132
cli,
131133
stderr=stderr,
@@ -151,8 +153,8 @@ def run() -> subprocess.Popen[bytes]:
151153
raise BrowserFailedError(
152154
"The browser seemed to close immediately after starting.",
153155
"You can set the `logging.Logger` level lower to see more output.",
154-
"You may try installed a known working copy of chrome from ",
155-
"`$ choreo_get_chome`. It may be your copy auto-updated.",
156+
"You may try installing a known working copy of Chrome by running ",
157+
"`$ choreo_get_chrome`. It may be your copy auto-updated.",
156158
) from e
157159

158160
async def __aenter__(self) -> Self:

choreographer/browsers/_interface_type.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def __init__(
2121
path: Path | str | None = None,
2222
**kwargs: Any,
2323
) -> None: ...
24+
def pre_open(self) -> None: ...
2425
def get_popen_args(self) -> Mapping[str, Any]: ...
2526
def get_cli(self) -> Sequence[str]: ...
2627
def get_env(self) -> MutableMapping[str, str]: ...

choreographer/browsers/chromium.py

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import argparse
56
import os
67
import platform
78
import re
@@ -30,8 +31,30 @@
3031
Path(__file__).resolve().parent / "_unix_pipe_chromium_wrapper.py"
3132
)
3233

34+
_packaged_chromium_libs = Path(__file__).resolve().parent / "packaged_chromium_libs"
35+
3336
_logger = logistro.getLogger(__name__)
3437

38+
_parser = argparse.ArgumentParser(add_help=False)
39+
_g = _parser.add_mutually_exclusive_group()
40+
_g.add_argument(
41+
"--ldd-fail",
42+
action="store_true",
43+
dest="ldd_fail",
44+
default="LDD_FAIL" in os.environ,
45+
help="Will cause to fail if not right deps.",
46+
)
47+
48+
_g.add_argument(
49+
"--force-packaged-deps",
50+
action="store_true",
51+
dest="force_deps",
52+
default="FORCE_PACKAGED_DEPS" in os.environ,
53+
help="Will force us to try local deps.",
54+
)
55+
56+
_args, _ = _parser.parse_known_args()
57+
3558

3659
def _is_exe(path: str | Path) -> bool:
3760
try:
@@ -85,6 +108,61 @@ def logger_parser(
85108
# we just eliminate their stamp, we dont' extract it
86109
return True
87110

111+
def _need_libs(self) -> bool: # noqa: C901 complexity
112+
if self.skip_local:
113+
_logger.debug(
114+
"If we HAVE to skip local.",
115+
)
116+
if _args.force_deps:
117+
_logger.warning(
118+
"We can NOT force deps in these security conditions, "
119+
"we must use locals.",
120+
)
121+
return False
122+
_logger.debug("Checking for libs needed.")
123+
if platform.system() != "Linux":
124+
_logger.debug("We're not in linux, so no need for check.")
125+
if _args.ldd_fail:
126+
_logger.warning("You asked for ldd-fail but we're not on linux.")
127+
if _args.force_deps:
128+
_logger.warning("You asked for packages deps but we're not on linux.")
129+
return False
130+
if _args.force_deps:
131+
_logger.debug("Force using packaged deps.")
132+
return True
133+
p = None
134+
try:
135+
_logger.debug(f"Trying ldd {self.path}")
136+
p = subprocess.run( # noqa: S603, validating run with variables
137+
[ # noqa: S607 path is all we have
138+
"ldd",
139+
str(self.path),
140+
],
141+
capture_output=True,
142+
timeout=5,
143+
check=True,
144+
)
145+
except BaseException as e:
146+
msg = "ldd failed."
147+
if _args.ldd_fail:
148+
_logger.exception(msg)
149+
raise
150+
else:
151+
stderr = p.stderr.decode() if p and p.stderr else None
152+
_logger.warning(
153+
msg # noqa: G003 + in log
154+
+ f" e: {e}, stderr: {stderr}",
155+
)
156+
return True
157+
if b"not found" in p.stdout:
158+
msg = "Found deps missing in chrome"
159+
if _args.ldd_fail:
160+
raise RuntimeError(msg + f" {p.stdout.decode()}")
161+
_logger.debug(msg + f" {p.stdout.decode()}") # noqa: G003 + in log
162+
return True
163+
_logger.debug("No problems found with dependencies")
164+
return False
165+
88166
def __init__(
89167
self,
90168
channel: ChannelInterface,
@@ -144,12 +222,15 @@ def __init__(
144222
"please see documentation.",
145223
)
146224
_logger.info(f"Found chromium path: {self.path}")
225+
147226
self._channel = channel
148227
if not isinstance(channel, Pipe):
149228
raise NotImplementedError("Websocket style channels not implemented yet.")
150229

151230
self._is_isolated = "snap" in str(self.path)
152231

232+
def pre_open(self) -> None:
233+
"""Prepare browser for opening."""
153234
self.tmp_dir = TmpDirectory(
154235
path=self._tmp_dir_path,
155236
sneak=self._is_isolated,
@@ -247,8 +328,14 @@ def get_cli(self) -> Sequence[str]:
247328

248329
def get_env(self) -> MutableMapping[str, str]:
249330
"""Return the env needed for chromium."""
250-
_logger.debug("Returning env: same env, no modification.")
251-
return os.environ.copy()
331+
env = os.environ.copy()
332+
if self._need_libs():
333+
original = env.get("LD_LIBRARY_PATH", "")
334+
env["LD_LIBRARY_PATH"] = f"{_packaged_chromium_libs!s}:{original}"
335+
_logger.debug(
336+
f"Added LD_LIBRARY_PATH={env['LD_LIBRARY_PATH']!s} to env vars.",
337+
)
338+
return env
252339

253340
def clean(self) -> None:
254341
"""Clean up any leftovers form browser, like tmp files."""
10.1 KB
Binary file not shown.
14 KB
Binary file not shown.
71.7 KB
Binary file not shown.
22.1 KB
Binary file not shown.

0 commit comments

Comments
 (0)