Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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.
9 changes: 9 additions & 0 deletions src/virtualenv/activation/bash/activate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ deactivate () {
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
if ! [ -z "${_OLD_PKG_CONFIG_PATH:+_}" ]; then
PKG_CONFIG_PATH="$_OLD_PKG_CONFIG_PATH"
export PKG_CONFIG_PATH
unset _OLD_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 +60,10 @@ _OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"__BIN_NAME__":$PATH"
export PATH

_OLD_PKG_CONFIG_PATH="${PKG_CONFIG_PATH}"
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 not defined _OLD_VIRTUAL_PKG_CONFIG_PATH @set "_OLD_VIRTUAL_PKG_CONFIG_PATH=%PKG_CONFIG_PATH%"
@if defined 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
5 changes: 5 additions & 0 deletions src/virtualenv/activation/batch/deactivate.bat
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
@set _OLD_VIRTUAL_PYTHONHOME=
:ENDIFVHOME

@if not defined _OLD_VIRTUAL_PKG_CONFIG_PATH @goto ENDIFPKGPATH
@set "PKG_CONFIG_PATH=%_OLD_VIRTUAL_PKG_CONFIG_PATH%"
@set _OLD_VIRTUAL_PKG_CONFIG_PATH=
:ENDIFPKGPATH

@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,11 @@ 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"
set -gx PKG_CONFIG_PATH $_OLD_VIRTUAL_PKG_CONFIG_PATH
set -e _OLD_VIRTUAL_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 +73,13 @@ 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
set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig" $PKG_CONFIG_PATH
else
set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig"
end

# 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'] | path join)
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
12 changes: 12 additions & 0 deletions src/virtualenv/activation/powershell/activate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ 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
}

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 +52,13 @@ else {
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH

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

New-Variable -Scope global -Name _OLD_VIRTUAL_PKG_CONFIG_PATH -Value $env:PKG_CONFIG_PATH
if ($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
17 changes: 17 additions & 0 deletions tests/unit/activation/test_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,21 @@ def activate_call(self, script):
def print_prompt(self):
return self.print_os_env_var("PS1")

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" # shell has no value
super().assert_output(out[:3] + out[4:9] + out[10:-2] + [out[-1]], 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
19 changes: 13 additions & 6 deletions tests/unit/activation/test_python_activator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def print_r(value):
print_r(os.environ.get("VIRTUAL_ENV"))
print_r(os.environ.get("VIRTUAL_ENV_PROMPT"))
print_r(os.environ.get("PATH").split(os.pathsep))
print_r(os.environ.get("PKG_CONFIG_PATH"))
print_r(sys.path)

file_at = {str(activate_script)!r}
Expand All @@ -52,6 +53,7 @@ def print_r(value):
print_r(os.environ.get("VIRTUAL_ENV"))
print_r(os.environ.get("VIRTUAL_ENV_PROMPT"))
print_r(os.environ.get("PATH").split(os.pathsep))
print_r(os.environ.get("PKG_CONFIG_PATH"))
print_r(sys.path)

import pydoc_test
Expand All @@ -63,26 +65,31 @@ def assert_output(self, out, raw, tmp_path): # noqa: ARG002
out = [literal_eval(i) for i in out]
assert out[0] is None # start with VIRTUAL_ENV None
assert out[1] is None # likewise for VIRTUAL_ENV_PROMPT
assert out[3] is None # PKG_CONFIG_PATH is None

prev_path = out[2]
prev_sys_path = out[3]
assert out[4] == str(self._creator.dest) # VIRTUAL_ENV now points to the virtual env folder
prev_sys_path = out[4]
assert out[5] == str(self._creator.dest) # VIRTUAL_ENV now points to the virtual env folder

assert out[5] == str(self._creator.env_name) # VIRTUAL_ENV_PROMPT now has the env name
assert out[6] == str(self._creator.env_name) # VIRTUAL_ENV_PROMPT now has the env name

new_path = out[6] # PATH now starts with bin path of current
new_path = out[7] # PATH now starts with bin path of current
assert ([str(self._creator.bin_dir), *prev_path]) == new_path

# PKG_CONFIG_PATH is set
pkg_config_path = self.norm_path(self._creator.dest / "lib" / "pkgconfig")
assert self.norm_path(out[8]) == pkg_config_path

# sys path contains the site package at its start
new_sys_path = out[7]
new_sys_path = out[9]

new_lib_paths = {str(i) for i in self._creator.libs}
assert prev_sys_path == new_sys_path[len(new_lib_paths) :]
assert new_lib_paths == set(new_sys_path[: len(new_lib_paths)])

# manage to import from activate site package
dest = self.norm_path(self._creator.purelib / "pydoc_test.py")
found = self.norm_path(out[8])
found = self.norm_path(out[10])
assert found.startswith(dest)

def non_source_activate(self, activate_script):
Expand Down
Loading