Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QOL: Config run should handle shell paths with spaces #989

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions invoke/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
ready_for_reading,
bytes_to_read,
)
from .shims import get_short_path_name
from .util import has_fileno, isatty, ExceptionHandlingThread

if TYPE_CHECKING:
Expand Down Expand Up @@ -415,6 +416,7 @@ def _setup(self, command: str, kwargs: Any) -> None:
# Echo running command (wants to be early to be included in dry-run)
if self.opts["echo"]:
self.echo(command)
self.opts["shell"] = get_short_path_name(self.opts["shell"])
# Prepare common result args.
# TODO: I hate this. Needs a deeper separate think about tweaking
# Runner.generate_result in a way that isn't literally just this same
Expand Down
54 changes: 54 additions & 0 deletions invoke/shims.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import sys


if sys.platform == "win32":
from ctypes import (

Check warning on line 5 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L5

Added line #L5 was not covered by tests
windll,
wintypes,
create_unicode_buffer,
)

def _get_short_path_name(long_path: str) -> str:

Check warning on line 11 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L11

Added line #L11 was not covered by tests
# Access `GetShortPathNameW()` function from `kernel32.dll`.
GetShortPathNameW = windll.kernel32.GetShortPathNameW
GetShortPathNameW.argtypes = [

Check warning on line 14 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L13-L14

Added lines #L13 - L14 were not covered by tests
wintypes.LPCWSTR,
wintypes.LPWSTR,
wintypes.DWORD,
]
GetShortPathNameW.restype = wintypes.DWORD

Check warning on line 19 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L19

Added line #L19 was not covered by tests
# Call function to get short path form.
buffer_size = 0
while True:
buffer_array = create_unicode_buffer(buffer_size)
required_size = GetShortPathNameW(

Check warning on line 24 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L21-L24

Added lines #L21 - L24 were not covered by tests
long_path,
buffer_array,
buffer_size,
)
if required_size > buffer_size:
buffer_size = required_size

Check warning on line 30 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L30

Added line #L30 was not covered by tests
else:
return buffer_array.value

Check warning on line 32 in invoke/shims.py

View check run for this annotation

Codecov / codecov/patch

invoke/shims.py#L32

Added line #L32 was not covered by tests

else:

def _get_short_path_name(long_path: str) -> str:
return long_path


def get_short_path_name(long_path: str) -> str:
"""
Get short path form for long path.

Only applies to Windows-based systems; on Unix this is a pass-thru.

.. note::
This API converts path strings to the 8.3 format used by earlier
tools on the Windows platform. This format has no spaces.

:param long_path: Long path such as `shutil.which()` results.

:returns: `str` Short path form of the long path.
"""
return _get_short_path_name(long_path)
8 changes: 8 additions & 0 deletions tests/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ def may_be_configured(self):
runner = self._runner(run={"shell": "/bin/tcsh"})
assert runner.run(_).shell == "/bin/tcsh"

def may_be_configured_with_short_path_on_windows(self):
skip()

@skip_if_windows
def may_be_configured_with_passthru_on_posix(self):
runner = self._runner(run={"shell": "/foo/bar/baz /bang"})
assert runner.run(_).shell == "/foo/bar/baz /bang"

def kwarg_beats_config(self):
runner = self._runner(run={"shell": "/bin/tcsh"})
assert runner.run(_, shell="/bin/zsh").shell == "/bin/zsh"
Expand Down
15 changes: 14 additions & 1 deletion tests/terminals.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
from unittest.mock import Mock, patch
from pytest import skip, mark

from invoke.terminals import pty_size, bytes_to_read, WINDOWS
from invoke.shims import get_short_path_name
from invoke.terminals import (
WINDOWS,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Objects should precede functions.

bytes_to_read,
pty_size,
)

# Skip on Windows CI, it may blow up on one of these tests
pytestmark = mark.skipif(
Expand Down Expand Up @@ -76,3 +81,11 @@ def returns_FIONREAD_result_when_stream_is_a_tty(self):

def returns_1_on_windows(self):
skip()

class get_short_path_name_:
def returns_passthru_on_posix(self):
short_path = "/foo/bar/baz"
assert get_short_path_name(short_path) == short_path

def returns_shortpath_on_windows(self):
skip()