diff --git a/.gitattributes b/.gitattributes index 69b47b5ad..c925e269f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,5 @@ *.bat text eol=crlf +*.ps1 text eol=lf +*.fish text eol=lf +*.csh text eol=lf +*.sh text eol=lf diff --git a/docs/changelog/1818.bugfix.rst b/docs/changelog/1818.bugfix.rst new file mode 100644 index 000000000..8396cb9f4 --- /dev/null +++ b/docs/changelog/1818.bugfix.rst @@ -0,0 +1 @@ +For activation scripts always use UNIX line endings (unless it's BATCH shell related) - by :user:`saytosid`. diff --git a/src/virtualenv/activation/powershell/activate.ps1 b/src/virtualenv/activation/powershell/activate.ps1 index 85b210308..a370a63f5 100644 --- a/src/virtualenv/activation/powershell/activate.ps1 +++ b/src/virtualenv/activation/powershell/activate.ps1 @@ -1,60 +1,60 @@ -$script:THIS_PATH = $myinvocation.mycommand.path -$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent - -function global:deactivate([switch] $NonDestructive) { - if (Test-Path variable:_OLD_VIRTUAL_PATH) { - $env:PATH = $variable:_OLD_VIRTUAL_PATH - Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global - } - - if (Test-Path function:_old_virtual_prompt) { - $function:prompt = $function:_old_virtual_prompt - Remove-Item function:\_old_virtual_prompt - } - - if ($env:VIRTUAL_ENV) { - Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue - } - - if (!$NonDestructive) { - # Self destruct! - Remove-Item function:deactivate - Remove-Item function:pydoc - } -} - -function global:pydoc { - python -m pydoc $args -} - -# unset irrelevant variables -deactivate -nondestructive - -$VIRTUAL_ENV = $BASE_DIR -$env:VIRTUAL_ENV = $VIRTUAL_ENV - -New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH - -$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH -if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { - function global:_old_virtual_prompt { - "" - } - $function:_old_virtual_prompt = $function:prompt - - if ("__VIRTUAL_PROMPT__" -ne "") { - function global:prompt { - # Add the custom prefix to the existing prompt - $previous_prompt_value = & $function:_old_virtual_prompt - ("__VIRTUAL_PROMPT__" + $previous_prompt_value) - } - } - else { - function global:prompt { - # Add a prefix to the current prompt, but don't discard it. - $previous_prompt_value = & $function:_old_virtual_prompt - $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " - ($new_prompt_value + $previous_prompt_value) - } - } -} +$script:THIS_PATH = $myinvocation.mycommand.path +$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent + +function global:deactivate([switch] $NonDestructive) { + if (Test-Path variable:_OLD_VIRTUAL_PATH) { + $env:PATH = $variable:_OLD_VIRTUAL_PATH + Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global + } + + if (Test-Path function:_old_virtual_prompt) { + $function:prompt = $function:_old_virtual_prompt + Remove-Item function:\_old_virtual_prompt + } + + if ($env:VIRTUAL_ENV) { + Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue + } + + if (!$NonDestructive) { + # Self destruct! + Remove-Item function:deactivate + Remove-Item function:pydoc + } +} + +function global:pydoc { + python -m pydoc $args +} + +# unset irrelevant variables +deactivate -nondestructive + +$VIRTUAL_ENV = $BASE_DIR +$env:VIRTUAL_ENV = $VIRTUAL_ENV + +New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH + +$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH +if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { + function global:_old_virtual_prompt { + "" + } + $function:_old_virtual_prompt = $function:prompt + + if ("__VIRTUAL_PROMPT__" -ne "") { + function global:prompt { + # Add the custom prefix to the existing prompt + $previous_prompt_value = & $function:_old_virtual_prompt + ("__VIRTUAL_PROMPT__" + $previous_prompt_value) + } + } + else { + function global:prompt { + # Add a prefix to the current prompt, but don't discard it. + $previous_prompt_value = & $function:_old_virtual_prompt + $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " + ($new_prompt_value + $previous_prompt_value) + } + } +} diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py index 7a9d3c8e6..14f097973 100644 --- a/src/virtualenv/activation/via_template.py +++ b/src/virtualenv/activation/via_template.py @@ -11,9 +11,9 @@ from .activator import Activator if sys.version_info >= (3, 7): - from importlib.resources import read_text + from importlib.resources import read_binary else: - from importlib_resources import read_text + from importlib_resources import read_binary @add_metaclass(ABCMeta) @@ -44,7 +44,8 @@ def _generate(self, replacements, templates, to_folder, creator): for template in templates: text = self.instantiate_template(replacements, template, creator) dest = to_folder / self.as_name(template) - dest.write_text(text, encoding="utf-8") + # use write_bytes to avoid platform specific line normalization (\n -> \r\n) + dest.write_bytes(text.encode("utf-8")) generated.append(dest) return generated @@ -52,8 +53,9 @@ def as_name(self, template): return template.name def instantiate_template(self, replacements, template, creator): - # read text and do replacements - text = read_text(self.__module__, str(template), encoding="utf-8", errors="strict") + # read content as binary to avoid platform specific line normalization (\n -> \r\n) + binary = read_binary(self.__module__, str(template)) + text = binary.decode("utf-8", errors="strict") for key, value in replacements.items(): value = self._repr_unicode(creator, value) text = text.replace(key, value) diff --git a/src/virtualenv/create/via_global_ref/api.py b/src/virtualenv/create/via_global_ref/api.py index c9eab3c6e..6f296f452 100644 --- a/src/virtualenv/create/via_global_ref/api.py +++ b/src/virtualenv/create/via_global_ref/api.py @@ -8,6 +8,7 @@ from virtualenv.info import fs_supports_symlink from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text from ..creator import Creator, CreatorMeta @@ -91,10 +92,10 @@ def install_patch(self): text = self.env_patch_text() if text: pth = self.purelib / "_virtualenv.pth" - logging.debug("create virtualenv import hook file %s", pth) + logging.debug("create virtualenv import hook file %s", ensure_text(str(pth))) pth.write_text("import _virtualenv") dest_path = self.purelib / "_virtualenv.py" - logging.debug("create %s", dest_path) + logging.debug("create %s", ensure_text(str(dest_path))) dest_path.write_text(text) def env_patch_text(self): diff --git a/src/virtualenv/util/path/_pathlib/via_os_path.py b/src/virtualenv/util/path/_pathlib/via_os_path.py index d11aeaaab..ac78d4f00 100644 --- a/src/virtualenv/util/path/_pathlib/via_os_path.py +++ b/src/virtualenv/util/path/_pathlib/via_os_path.py @@ -87,9 +87,12 @@ def read_bytes(self): with open(self._path, "rb") as file_handler: return file_handler.read() - def write_text(self, text, encoding="utf-8"): + def write_bytes(self, content): with open(self._path, "wb") as file_handler: - file_handler.write(text.encode(encoding)) + file_handler.write(content) + + def write_text(self, text, encoding="utf-8"): + self.write_bytes(text.encode(encoding)) def iterdir(self): for p in os.listdir(self._path): diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index 98c3fda55..bb3e292fd 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -31,6 +31,7 @@ def __init__(self, of_class, session, cmd, activate_script, extension): self.pydoc_call = "pydoc -w pydoc_test" self.script_encoding = "utf-8" self._version = None + self.unix_line_ending = True def get_version(self, raise_on_fail): if self._version is None: @@ -63,6 +64,16 @@ def __repr__(self): def __call__(self, monkeypatch, tmp_path): activate_script = self._creator.bin_dir / self.activate_script + + # check line endings are correct type + script_content = activate_script.read_bytes() + for line in script_content.split(b"\n")[:-1]: + cr = b"\r" if sys.version_info.major == 2 else 13 + if self.unix_line_ending: + assert line == b"" or line[-1] != cr, script_content.decode("utf-8") + else: + assert line[-1] == cr, script_content.decode("utf-8") + test_script = self._generate_test_script(activate_script, tmp_path) monkeypatch.chdir(ensure_text(str(tmp_path))) diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py index 5246be09a..51498edbb 100644 --- a/tests/unit/activation/test_bash.py +++ b/tests/unit/activation/test_bash.py @@ -1,13 +1,12 @@ from __future__ import absolute_import, unicode_literals -import sys - import pytest from virtualenv.activation import BashActivator +from virtualenv.info import IS_WIN -@pytest.mark.skipif(sys.platform == "win32", reason="Github Actions ships with WSL bash") +@pytest.mark.skipif(IS_WIN, reason="Github Actions ships with WSL bash") def test_bash(raise_on_non_source_class, activation_tester): class Bash(raise_on_non_source_class): def __init__(self, session): diff --git a/tests/unit/activation/test_batch.py b/tests/unit/activation/test_batch.py index 92b141b03..e10d902a1 100644 --- a/tests/unit/activation/test_batch.py +++ b/tests/unit/activation/test_batch.py @@ -17,6 +17,7 @@ def __init__(self, session): self.deactivate = "call deactivate" self.activate_cmd = "call" self.pydoc_call = "call {}".format(self.pydoc_call) + self.unix_line_ending = False def _get_test_lines(self, activate_script): # for BATCH utf-8 support need change the character code page to 650001 diff --git a/tests/unit/activation/test_python_activator.py b/tests/unit/activation/test_python_activator.py index 6abf255aa..859fad684 100644 --- a/tests/unit/activation/test_python_activator.py +++ b/tests/unit/activation/test_python_activator.py @@ -6,7 +6,7 @@ from textwrap import dedent from virtualenv.activation import PythonActivator -from virtualenv.info import WIN_CPYTHON_2 +from virtualenv.info import IS_WIN, WIN_CPYTHON_2 from virtualenv.util.six import ensure_text @@ -21,6 +21,7 @@ def __init__(self, session): extension="py", non_source_fail_message="You must use exec(open(this_file).read(), {'__file__': this_file}))", ) + self.unix_line_ending = not IS_WIN def env(self, tmp_path): env = os.environ.copy()