Skip to content

Commit

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

More work towards pex-tool#2658.
  • Loading branch information
jsirois committed Feb 24, 2025
1 parent 75fb09c commit f89afaa
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 17 deletions.
28 changes: 23 additions & 5 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 All @@ -33,6 +33,7 @@
Dict,
Iterable,
Iterator,
List,
NoReturn,
Optional,
Sequence,
Expand Down Expand Up @@ -91,19 +92,24 @@ def try_open_file(
):
# type: (...) -> Result

args = [] # type: List[str]
url = None # type: Optional[str]
if open_program:
opener = open_program
elif LINUX:
elif WINDOWS:
opener = os.environ.get("COMSPEC", "cmd.exe")
args.append("/C")
elif MAC:
opener = "open"
else:
opener = "xdg-open"
url = "https://www.freedesktop.org/wiki/Software/xdg-utils/"
else:
opener = "open"
args.append(path)

with open(os.devnull, "wb") as devnull:
return try_run_program(
opener,
[path],
args,
url=url,
error=error,
disown=True,
Expand All @@ -112,6 +118,18 @@ def try_open_file(
)


def try_open_url(
url, # type: str
open_program=None, # type: Optional[str]
error=None, # type: Optional[str]
suppress_stderr=False, # type: bool
):
# type: (...) -> Result
return try_open_file(
url, open_program=open_program or "explorer", error=error, suppress_stderr=suppress_stderr
)


@attr.s(frozen=True)
class Command(object):
@staticmethod
Expand Down
4 changes: 2 additions & 2 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_url
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 @@ -92,7 +92,7 @@ def serve_html_docs(

if open_browser:
try_(
try_open_file(result.server_info.url, open_program=config.browser, suppress_stderr=True)
try_open_url(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

0 comments on commit f89afaa

Please sign in to comment.