Skip to content

Commit

Permalink
Fix docs server for Windows. (#2700)
Browse files Browse the repository at this point in the history
A bit hacky, but this seems to work well. Hack tracked in #2699.

More work towards #2658.
  • Loading branch information
jsirois authored Feb 24, 2025
1 parent 8013e01 commit 40a6c21
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 30 deletions.
30 changes: 17 additions & 13 deletions pex/commands/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pex.cache import access as cache_access
from pex.common import environment_as, safe_mkdtemp, safe_open
from pex.compatibility import shlex_quote
from pex.os import LINUX
from pex.os import MAC, WINDOWS
from pex.result import Error, Ok, Result
from pex.subprocess import subprocess_daemon_kwargs
from pex.typing import TYPE_CHECKING, Generic, cast
Expand Down Expand Up @@ -52,7 +52,7 @@
def try_run_program(
program, # type: str
args, # type: Iterable[str]
url=None, # type: Optional[str]
program_info_url=None, # type: Optional[str]
error=None, # type: Optional[str]
disown=False, # type: bool
**kwargs # type: Any
Expand All @@ -76,35 +76,39 @@ def try_run_program(
except OSError as e:
msg = [error] if error else []
msg.append("Do you have `{}` installed on the $PATH?: {}".format(program, e))
if url:
if program_info_url:
msg.append(
"Find more information on `{program}` at {url}.".format(program=program, url=url)
"Find more information on `{program}` at {url}.".format(
program=program, url=program_info_url
)
)
return Error("\n".join(msg))


def try_open_file(
path, # type: str
def try_open(
path_or_url, # type: str
open_program=None, # type: Optional[str]
error=None, # type: Optional[str]
suppress_stderr=False, # type: bool
):
# type: (...) -> Result

url = None # type: Optional[str]
program_info_url = None # type: Optional[str]
if open_program:
opener = open_program
elif LINUX:
opener = "xdg-open"
url = "https://www.freedesktop.org/wiki/Software/xdg-utils/"
else:
elif WINDOWS:
opener = "explorer"
elif MAC:
opener = "open"
else:
opener = "xdg-open"
program_info_url = "https://www.freedesktop.org/wiki/Software/xdg-utils/"

with open(os.devnull, "wb") as devnull:
return try_run_program(
opener,
[path],
url=url,
[path_or_url],
program_info_url=program_info_url,
error=error,
disown=True,
stdout=devnull,
Expand Down
6 changes: 2 additions & 4 deletions pex/docs/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from textwrap import dedent

from pex import docs
from pex.commands.command import try_open_file
from pex.commands.command import try_open
from pex.docs.server import SERVER_NAME, LaunchError, LaunchResult
from pex.docs.server import launch as launch_docs_server
from pex.result import Error, try_
Expand Down Expand Up @@ -91,8 +91,6 @@ def serve_html_docs(
return Error("Failed to launch {server}.".format(server=SERVER_NAME))

if open_browser:
try_(
try_open_file(result.server_info.url, open_program=config.browser, suppress_stderr=True)
)
try_(try_open(result.server_info.url, open_program=config.browser, suppress_stderr=True))

return result
13 changes: 3 additions & 10 deletions pex/docs/server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright 2024 Pex project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import errno
import json
import logging
import os
Expand All @@ -13,7 +12,7 @@

from pex.cache.dirs import CacheDir
from pex.common import safe_open
from pex.os import kill
from pex.os import is_alive, kill
from pex.subprocess import launch_python_daemon
from pex.typing import TYPE_CHECKING
from pex.version import __version__
Expand Down Expand Up @@ -79,7 +78,7 @@ def _read_url(
with open(server_log) as fp:
for line in fp:
if line.endswith(("\r", "\n")):
match = re.search(r"Serving HTTP on 0\.0\.0\.0 port (?P<port>\d+)", line)
match = re.search(r"Serving HTTP on \S+ port (?P<port>\d+)", line)
if match:
port = match.group("port")
return "http://localhost:{port}".format(port=port)
Expand All @@ -106,13 +105,7 @@ def record(
def alive(self):
# type: () -> bool
# TODO(John Sirois): Handle pid rollover
try:
os.kill(self.server_info.pid, 0)
return True
except OSError as e:
if e.errno == errno.ESRCH: # No such process.
return False
raise
return is_alive(self.server_info.pid)

def kill(self):
# type: () -> None
Expand Down
56 changes: 56 additions & 0 deletions pex/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,49 @@ def is_exe(path):

if WINDOWS:

def is_alive(pid):
# type: (int) -> bool

# TODO(John Sirois): This is extremely hacky, consider adding a psutil dependency for
# Windows. See: https://github.com/pex-tool/pex/issues/2699

import csv
import subprocess

args = ["tasklist", "/FI", "PID eq {pid}".format(pid=pid), "/FO", "CSV"]
process = subprocess.Popen(args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode != 0:
raise RuntimeError(
"Failed to query status of process with pid {pid}.\n"
"Execution of `{args}` returned exit code {returncode}.\n"
"{stderr}".format(
pid=pid,
args=" ".join(args),
returncode=process.returncode,
stderr=stderr.decode("utf-8"),
)
)

output = stdout.decode("utf-8")
if "No tasks are running" in output:
return False

lines = output.splitlines()
if len(lines) != 2:
return False

csv_reader = csv.DictReader(lines)
for row in csv_reader:
pid_value = row.get("PID", -1)
if pid_value == -1:
return False
try:
return pid == int(pid_value)
except (ValueError, TypeError):
return False
return False

# https://learn.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
_PROCESS_TERMINATE = 0x1 # Required to terminate a process using TerminateProcess.

Expand Down Expand Up @@ -170,6 +213,19 @@ def kill(pid):

else:

def is_alive(pid):
# type: (int) -> bool

import errno

try:
os.kill(pid, 0)
return True
except OSError as e:
if e.errno == errno.ESRCH: # No such process.
return False
raise

def kill(pid):
# type: (int) -> None

Expand Down
6 changes: 3 additions & 3 deletions pex/tools/commands/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from argparse import ArgumentParser
from contextlib import contextmanager

from pex.commands.command import OutputMixin, try_open_file, try_run_program
from pex.commands.command import OutputMixin, try_open, try_run_program
from pex.common import safe_mkdir
from pex.dist_metadata import requires_dists
from pex.interpreter_constraints import InterpreterConstraint
Expand Down Expand Up @@ -124,7 +124,7 @@ def emit():
try:
return try_run_program(
"dot",
url="https://graphviz.org/",
program_info_url="https://graphviz.org/",
error="Failed to render dependency graph for {}.".format(graph.name),
args=["-T", self.options.format],
stdin=read_fd,
Expand Down Expand Up @@ -168,7 +168,7 @@ def run(self, pex):
if result.is_error:
return result

return try_open_file(
return try_open(
open_path,
error="Failed to open dependency graph of {} rendered in {} for viewing.".format(
pex.path(), open_path
Expand Down

0 comments on commit 40a6c21

Please sign in to comment.