Skip to content
Closed
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 docs/changelog/2637.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for `pkg-config` to virtualenv by adding the virtual environment's `lib/pkgconfig` directory to the `PKG_CONFIG_PATH` environment variable upon activation.
Contributed by :user:esafak.
14 changes: 14 additions & 0 deletions src/virtualenv/activation/bash/activate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ deactivate () {
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
if [ -n "${_OLD_PKG_CONFIG_PATH-}" ] ; then
PKG_CONFIG_PATH="$_OLD_PKG_CONFIG_PATH"
export PKG_CONFIG_PATH
unset _OLD_PKG_CONFIG_PATH
else
# if PKG_CONFIG_PATH was not set before, we should unset it
unset PKG_CONFIG_PATH
fi

# The hash command must be called to get it to forget past
# commands. Without forgetting past commands the $PATH changes
Expand Down Expand Up @@ -55,6 +63,12 @@ _OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"__BIN_NAME__":$PATH"
export PATH

if [ -n "${PKG_CONFIG_PATH-}" ] ; then
_OLD_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
fi
PKG_CONFIG_PATH="$VIRTUAL_ENV/lib/pkgconfig${PKG_CONFIG_PATH:+:}${PKG_CONFIG_PATH}"
export PKG_CONFIG_PATH

if [ "x"__VIRTUAL_PROMPT__ != x ] ; then
VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__
else
Expand Down
7 changes: 7 additions & 0 deletions src/virtualenv/activation/batch/activate.bat
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@

@set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%"

@if defined PKG_CONFIG_PATH (
@set "_OLD_VIRTUAL_PKG_CONFIG_PATH=%PKG_CONFIG_PATH%"
@set "PKG_CONFIG_PATH=%VIRTUAL_ENV%\lib\pkgconfig;%PKG_CONFIG_PATH%"
) else (
@set "PKG_CONFIG_PATH=%VIRTUAL_ENV%\lib\pkgconfig"
)

@if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
@set _OLD_CODEPAGE=
Expand Down
7 changes: 7 additions & 0 deletions src/virtualenv/activation/batch/deactivate.bat
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
@set _OLD_VIRTUAL_PYTHONHOME=
:ENDIFVHOME

@if defined _OLD_VIRTUAL_PKG_CONFIG_PATH (
@set "PKG_CONFIG_PATH=%_OLD_VIRTUAL_PKG_CONFIG_PATH%"
@set _OLD_VIRTUAL_PKG_CONFIG_PATH=
) else (
@set PKG_CONFIG_PATH=
)

@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH
@set "PATH=%_OLD_VIRTUAL_PATH%"
@set _OLD_VIRTUAL_PATH=
Expand Down
9 changes: 8 additions & 1 deletion src/virtualenv/activation/cshell/activate.csh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
set newline='\
'

alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; test $?_OLD_VIRTUAL_PKG_CONFIG_PATH != 0 && setenv PKG_CONFIG_PATH "$_OLD_VIRTUAL_PKG_CONFIG_PATH:q" && unset _OLD_VIRTUAL_PKG_CONFIG_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'

# Unset irrelevant variables.
deactivate nondestructive
Expand All @@ -15,6 +15,13 @@ setenv VIRTUAL_ENV __VIRTUAL_ENV__
set _OLD_VIRTUAL_PATH="$PATH:q"
setenv PATH "$VIRTUAL_ENV:q/"__BIN_NAME__":$PATH:q"

if ( $?PKG_CONFIG_PATH ) then
set _OLD_VIRTUAL_PKG_CONFIG_PATH="$PKG_CONFIG_PATH:q"
setenv PKG_CONFIG_PATH "$VIRTUAL_ENV:q/lib/pkgconfig:$PKG_CONFIG_PATH:q"
else
setenv PKG_CONFIG_PATH "$VIRTUAL_ENV:q/lib/pkgconfig"
endif



if (__VIRTUAL_PROMPT__ != "") then
Expand Down
12 changes: 12 additions & 0 deletions src/virtualenv/activation/fish/activate.fish
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ function deactivate -d 'Exit virtualenv mode and return to the normal environmen
set -e _OLD_VIRTUAL_PYTHONHOME
end

if test -n "$_OLD_VIRTUAL_PKG_CONFIG_PATH" -o -n "$_OLD_VIRTUAL_PKG_CONFIG_PATH_BACKUP"
set -gx PKG_CONFIG_PATH $_OLD_VIRTUAL_PKG_CONFIG_PATH
set -e _OLD_VIRTUAL_PKG_CONFIG_PATH
else
set -e PKG_CONFIG_PATH
end

if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
and functions -q _old_fish_prompt
# Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`.
Expand Down Expand Up @@ -68,6 +75,11 @@ else
end
set -gx PATH "$VIRTUAL_ENV"'/'__BIN_NAME__ $PATH

if set -q PKG_CONFIG_PATH
set -gx _OLD_VIRTUAL_PKG_CONFIG_PATH $PKG_CONFIG_PATH
end
set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig" $PKG_CONFIG_PATH

# Prompt override provided?
# If not, just use the environment name.
if test -n __VIRTUAL_PROMPT__
Expand Down
3 changes: 3 additions & 0 deletions src/virtualenv/activation/nushell/activate.nu
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export-env {
}
$new_env | merge { PROMPT_COMMAND: $new_prompt VIRTUAL_PREFIX: $virtual_prefix }
}
let pkg_config_path = ([$virtual_env "lib" "pkgconfig"] | str join (path sep))
let new_pkg_config_path = ($env | get --optional PKG_CONFIG_PATH | default [] | prepend $pkg_config_path)
let new_env = ($new_env | upsert 'PKG_CONFIG_PATH' $new_pkg_config_path)
load-env $new_env
}

Expand Down
14 changes: 14 additions & 0 deletions src/virtualenv/activation/powershell/activate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ function global:deactivate([switch] $NonDestructive) {
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
}

if (Test-Path variable:_OLD_VIRTUAL_PKG_CONFIG_PATH) {
$env:PKG_CONFIG_PATH = $variable:_OLD_VIRTUAL_PKG_CONFIG_PATH
Remove-Variable "_OLD_VIRTUAL_PKG_CONFIG_PATH" -Scope global
} else {
Remove-Item env:PKG_CONFIG_PATH -ErrorAction SilentlyContinue
}

if (Test-Path function:_old_virtual_prompt) {
$function:prompt = $function:_old_virtual_prompt
Remove-Item function:\_old_virtual_prompt
Expand Down Expand Up @@ -47,6 +54,13 @@ else {
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH

$env:PATH = "$env:VIRTUAL_ENV/" + __BIN_NAME__ + __PATH_SEP__ + $env:PATH

if ($env:PKG_CONFIG_PATH) {
New-Variable -Scope global -Name _OLD_VIRTUAL_PKG_CONFIG_PATH -Value $env:PKG_CONFIG_PATH
$env:PKG_CONFIG_PATH = "$env:VIRTUAL_ENV\lib\pkgconfig" + __PATH_SEP__ + $env:PKG_CONFIG_PATH
} else {
$env:PKG_CONFIG_PATH = "$env:VIRTUAL_ENV\lib\pkgconfig"
}
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
function global:_old_virtual_prompt {
""
Expand Down
6 changes: 6 additions & 0 deletions src/virtualenv/activation/python/activate_this.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
os.environ["VIRTUAL_ENV_PROMPT"] = __VIRTUAL_PROMPT__ or os.path.basename(base)

# prepend pkg-config path
pkg_config_path = os.path.join(base, "lib", "pkgconfig")
os.environ["PKG_CONFIG_PATH"] = os.pathsep.join(
[pkg_config_path, *os.environ.get("PKG_CONFIG_PATH", "").split(os.pathsep)],
).rstrip(os.pathsep)

# add the virtual environments libraries to the host python import mechanism
prev_length = len(sys.path)
for lib in __LIB_FOLDERS__.split(os.pathsep):
Expand Down
24 changes: 16 additions & 8 deletions tests/unit/activation/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def env(self, tmp_path): # noqa: ARG002
env["PYTHONIOENCODING"] = "utf-8"
env["PATH"] = os.pathsep.join([dirname(sys.executable), *env.get("PATH", "").split(os.pathsep)])
# clear up some environment variables so they don't affect the tests
for key in [k for k in env if k.startswith(("_OLD", "VIRTUALENV_"))]:
for key in [k for k in env if k.startswith(("_OLD", "VIRTUALENV_")) or k == "PKG_CONFIG_PATH"]:
del env[key]
return env

Expand All @@ -115,17 +115,20 @@ def _get_test_lines(self, activate_script):
self.print_python_exe(),
self.print_os_env_var("VIRTUAL_ENV"),
self.print_os_env_var("VIRTUAL_ENV_PROMPT"),
self.print_os_env_var("PKG_CONFIG_PATH"),
self.activate_call(activate_script),
self.print_python_exe(),
self.print_os_env_var("VIRTUAL_ENV"),
self.print_os_env_var("VIRTUAL_ENV_PROMPT"),
self.print_os_env_var("PKG_CONFIG_PATH"),
self.print_prompt(),
# \\ loads documentation from the virtualenv site packages
self.pydoc_call,
self.deactivate,
self.print_python_exe(),
self.print_os_env_var("VIRTUAL_ENV"),
self.print_os_env_var("VIRTUAL_ENV_PROMPT"),
self.print_os_env_var("PKG_CONFIG_PATH"),
"", # just finish with an empty new line
]

Expand All @@ -134,23 +137,28 @@ def assert_output(self, out, raw, tmp_path):
assert out[0], raw
assert out[1] == "None", raw
assert out[2] == "None", raw
assert out[3] == "None", raw
# post-activation
expected = self._creator.exe.parent / os.path.basename(sys.executable)
assert self.norm_path(out[3]) == self.norm_path(expected), raw
assert self.norm_path(out[4]) == self.norm_path(self._creator.dest).replace("\\\\", "\\"), raw
assert out[5] == self._creator.env_name
assert self.norm_path(out[4]) == self.norm_path(expected), raw
assert self.norm_path(out[5]) == self.norm_path(self._creator.dest).replace("\\\\", "\\"), raw
assert out[6] == self._creator.env_name
pkg_config_path = self._creator.dest / "lib" / "pkgconfig"
assert self.norm_path(out[7]) == self.norm_path(pkg_config_path), raw

# Some attempts to test the prompt output print more than 1 line.
# So we need to check if the prompt exists on any of them.
prompt_text = f"({self._creator.env_name}) "
assert any(prompt_text in line for line in out[6:-4]), raw
assert any(prompt_text in line for line in out[8:-5]), raw

assert out[-4] == "wrote pydoc_test.html", raw
assert out[-5] == "wrote pydoc_test.html", raw
content = tmp_path / "pydoc_test.html"
assert content.exists(), raw
# post deactivation, same as before
assert out[-3] == out[0], raw
assert out[-4] == out[0], raw
assert out[-3] == "None", raw
assert out[-2] == "None", raw
assert out[-1] == "None", raw
assert out[-1] in {"None", ""}, raw

def quote(self, s):
return self.of_class.quote(s)
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/activation/test_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ def activate_call(self, script):
def print_prompt(self):
return self.print_os_env_var("PS1")

def assert_output(self, out, raw, tmp_path):
# for bash we check the prompt is changed
prompt_text = f"({self._creator.env_name}) "
assert out[8].startswith(prompt_text)
# then call the base to check the rest
super().assert_output(out, raw, tmp_path)

activation_tester(Bash)
16 changes: 15 additions & 1 deletion tests/unit/activation/test_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,21 @@ def __init__(self, session) -> None:
self.unix_line_ending = False

def _get_test_lines(self, activate_script):
return ["@echo off", *super()._get_test_lines(activate_script)]
lines = ["@echo off", *super()._get_test_lines(activate_script)]
lines.insert(4, self.print_os_env_var("PKG_CONFIG_PATH"))
i = next(i for i, line in enumerate(lines) if "pydoc" in line)
lines.insert(i, self.print_os_env_var("PKG_CONFIG_PATH"))
lines.insert(-1, self.print_os_env_var("PKG_CONFIG_PATH"))
return lines

def assert_output(self, out, raw, tmp_path):
assert out[3] == "None"

pkg_config_path = self.norm_path(self._creator.dest / "lib" / "pkgconfig")
assert self.norm_path(out[9]) == pkg_config_path

assert out[-2] == "None"
super().assert_output(out[:3] + out[4:9] + out[10:-2] + [out[-1]], raw, tmp_path)

def quote(self, s):
if '"' in s or " " in s:
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/activation/test_csh.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,21 @@ def print_prompt(self):
# breaking the test; hence the trailing echo.
return "echo 'source \"$VIRTUAL_ENV/bin/activate.csh\"; echo $prompt' | csh -i ; echo"

def _get_test_lines(self, activate_script):
lines = super()._get_test_lines(activate_script)
lines.insert(3, self.print_os_env_var("PKG_CONFIG_PATH"))
i = next(i for i, line in enumerate(lines) if "pydoc" in line)
lines.insert(i, self.print_os_env_var("PKG_CONFIG_PATH"))
lines.insert(-1, self.print_os_env_var("PKG_CONFIG_PATH"))
return lines

def assert_output(self, out, raw, tmp_path):
assert out[3] == "None"

pkg_config_path = self.norm_path(self._creator.dest / "lib" / "pkgconfig")
assert self.norm_path(out[9]) == pkg_config_path

assert out[-2] == "None"
super().assert_output(out[:3] + out[4:9] + out[10:-2] + [out[-1]], raw, tmp_path)

activation_tester(Csh)
18 changes: 13 additions & 5 deletions tests/unit/activation/test_fish.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ def _get_test_lines(self, activate_script):
self.print_os_env_var("VIRTUAL_ENV"),
self.print_os_env_var("VIRTUAL_ENV_PROMPT"),
self.print_os_env_var("PATH"),
self.print_os_env_var("PKG_CONFIG_PATH"),
self.activate_call(activate_script),
self.print_python_exe(),
self.print_os_env_var("VIRTUAL_ENV"),
self.print_os_env_var("VIRTUAL_ENV_PROMPT"),
self.print_os_env_var("PATH"),
self.print_os_env_var("PKG_CONFIG_PATH"),
self.print_prompt(),
# \\ loads documentation from the virtualenv site packages
self.pydoc_call,
Expand All @@ -42,6 +44,7 @@ def _get_test_lines(self, activate_script):
self.print_os_env_var("VIRTUAL_ENV"),
self.print_os_env_var("VIRTUAL_ENV_PROMPT"),
self.print_os_env_var("PATH"),
self.print_os_env_var("PKG_CONFIG_PATH"),
"", # just finish with an empty new line
]

Expand All @@ -50,15 +53,16 @@ def assert_output(self, out, raw, _):
assert out[0], raw
assert out[1] == "None", raw
assert out[2] == "None", raw
assert out[4] == "None", raw
# post-activation
expected = self._creator.exe.parent / os.path.basename(sys.executable)
assert self.norm_path(out[4]) == self.norm_path(expected), raw
assert self.norm_path(out[5]) == self.norm_path(self._creator.dest).replace("\\\\", "\\"), raw
assert out[6] == self._creator.env_name
assert self.norm_path(out[5]) == self.norm_path(expected), raw
assert self.norm_path(out[6]) == self.norm_path(self._creator.dest).replace("\\\\", "\\"), raw
assert out[7] == self._creator.env_name
# Some attempts to test the prompt output print more than 1 line.
# So we need to check if the prompt exists on any of them.
prompt_text = f"({self._creator.env_name}) "
assert any(prompt_text in line for line in out[7:-5]), raw
assert any(prompt_text in line for line in out[8:-5]), raw

assert out[-5] == "wrote pydoc_test.html", raw
content = tmp_path / "pydoc_test.html"
Expand All @@ -69,8 +73,12 @@ def assert_output(self, out, raw, _):
assert out[-2] == "None", raw

# Check that the PATH is restored
assert out[3] == out[13], raw
assert out[3] == out[15], raw
# Check that PATH changed after activation
assert out[3] != out[8], raw
# Check that PKG_CONFIG_PATH is restored
assert out[4] == out[16], raw
# Check that PKG_CONFIG_PATH changed after activation
assert out[4] != out[9], raw

activation_tester(Fish)
18 changes: 18 additions & 0 deletions tests/unit/activation/test_nushell.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def __init__(self, session) -> None:
super().__init__(NushellActivator, session, cmd, "activate.nu", "nu")

self.activate_cmd = "overlay use"
self.deactivate = "overlay hide activate"
self.unix_line_ending = not IS_WIN

def print_prompt(self):
Expand All @@ -27,4 +28,21 @@ def activate_call(self, script):
scr = self.quote(str(script))
return f"{cmd} {scr}".strip()

def _get_test_lines(self, activate_script):
lines = super()._get_test_lines(activate_script)
lines.insert(3, self.print_os_env_var("PKG_CONFIG_PATH"))
i = next(i for i, line in enumerate(lines) if "pydoc" in line)
lines.insert(i, self.print_os_env_var("PKG_CONFIG_PATH"))
lines.insert(-1, self.print_os_env_var("PKG_CONFIG_PATH"))
return lines

def assert_output(self, out, raw, tmp_path):
assert out[3] == "None"

pkg_config_path = self.norm_path(self._creator.dest / "lib" / "pkgconfig")
assert self.norm_path(out[9]) == pkg_config_path

assert out[-2] == "None"
super().assert_output(out[:3] + out[4:9] + out[10:-2] + [out[-1]], raw, tmp_path)

activation_tester(Nushell)
16 changes: 15 additions & 1 deletion tests/unit/activation/test_powershell.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,21 @@ def __init__(self, session) -> None:
self.script_encoding = "utf-8-sig"

def _get_test_lines(self, activate_script):
return super()._get_test_lines(activate_script)
lines = super()._get_test_lines(activate_script)
lines.insert(3, self.print_os_env_var("PKG_CONFIG_PATH"))
i = next(i for i, line in enumerate(lines) if "pydoc" in line)
lines.insert(i, self.print_os_env_var("PKG_CONFIG_PATH"))
lines.insert(-1, self.print_os_env_var("PKG_CONFIG_PATH"))
return lines

def assert_output(self, out, raw, tmp_path):
assert out[3] == "None"

pkg_config_path = self.norm_path(self._creator.dest / "lib" / "pkgconfig")
assert self.norm_path(out[9]) == pkg_config_path

assert out[-2] == "None"
super().assert_output(out[:3] + out[4:9] + out[10:-2] + [out[-1]], raw, tmp_path)

def invoke_script(self):
return [self.cmd, "-File"]
Expand Down
Loading
Loading