Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions docs/changelog/2637.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``PKG_CONFIG_PATH`` environment variable support to all activation scripts (Bash, Batch, PowerShell, Fish, C Shell, Nushell, and Python). The virtualenv's ``lib/pkgconfig`` directory is now automatically prepended to ``PKG_CONFIG_PATH`` on activation and restored on deactivation, enabling packages that use ``pkg-config`` during build/install to find their configuration files - by :user:`rahuldevikar`.
9 changes: 9 additions & 0 deletions src/virtualenv/activation/bash/activate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ deactivate () {
export TK_LIBRARY
unset _OLD_VIRTUAL_TK_LIBRARY
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 @@ -74,6 +79,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}"
export PKG_CONFIG_PATH

if [ "x"__VIRTUAL_PROMPT__ != x ] ; then
VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__
else
Expand Down
3 changes: 3 additions & 0 deletions src/virtualenv/activation/batch/activate.bat
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
@if defined TK_LIBRARY @set "_OLD_VIRTUAL_TK_LIBRARY=%TK_LIBRARY%"
@if NOT "__TK_LIBRARY__"=="" @set "TK_LIBRARY=__TK_LIBRARY__"

@if defined PKG_CONFIG_PATH @set "_OLD_PKG_CONFIG_PATH=%PKG_CONFIG_PATH%"
@set "PKG_CONFIG_PATH=%VIRTUAL_ENV%\lib\pkgconfig;%PKG_CONFIG_PATH%"

@REM if defined _OLD_VIRTUAL_PATH (
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH1
@set "PATH=%_OLD_VIRTUAL_PATH%"
Expand Down
4 changes: 4 additions & 0 deletions src/virtualenv/activation/batch/deactivate.bat
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
@if not defined _OLD_VIRTUAL_TK_LIBRARY @set TK_LIBRARY=
@set _OLD_VIRTUAL_TK_LIBRARY=

@if defined _OLD_PKG_CONFIG_PATH @set "PKG_CONFIG_PATH=%_OLD_PKG_CONFIG_PATH%"
@if not defined _OLD_PKG_CONFIG_PATH @set PKG_CONFIG_PATH=
@set _OLD_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_TCL_LIBRARY != 0 && setenv TCL_LIBRARY "$_OLD_VIRTUAL_TCL_LIBRARY:q" && unset _OLD_VIRTUAL_TCL_LIBRARY || unsetenv TCL_LIBRARY; test $?_OLD_VIRTUAL_TK_LIBRARY != 0 && setenv TK_LIBRARY "$_OLD_VIRTUAL_TK_LIBRARY:q" && unset _OLD_VIRTUAL_TK_LIBRARY || unsetenv TK_LIBRARY; 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; rehash; test $?_OLD_VIRTUAL_TCL_LIBRARY != 0 && setenv TCL_LIBRARY "$_OLD_VIRTUAL_TCL_LIBRARY:q" && unset _OLD_VIRTUAL_TCL_LIBRARY || unsetenv TCL_LIBRARY; test $?_OLD_VIRTUAL_TK_LIBRARY != 0 && setenv TK_LIBRARY "$_OLD_VIRTUAL_TK_LIBRARY:q" && unset _OLD_VIRTUAL_TK_LIBRARY || unsetenv TK_LIBRARY; test $?_OLD_PKG_CONFIG_PATH != 0 && setenv PKG_CONFIG_PATH "$_OLD_PKG_CONFIG_PATH:q" && unset _OLD_PKG_CONFIG_PATH || unsetenv PKG_CONFIG_PATH; 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_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig:${PKG_CONFIG_PATH}"
else
setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig"
endif

if (__TCL_LIBRARY__ != "") then
if ($?TCL_LIBRARY) then
set _OLD_VIRTUAL_TCL_LIBRARY="$TCL_LIBRARY"
Expand Down
8 changes: 8 additions & 0 deletions src/virtualenv/activation/fish/activate.fish
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ function deactivate -d 'Exit virtualenv mode and return to the normal environmen
end
end

if test -n "$_OLD_PKG_CONFIG_PATH"
set -gx PKG_CONFIG_PATH "$_OLD_PKG_CONFIG_PATH"
set -e _OLD_PKG_CONFIG_PATH
end

if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME"
set -e _OLD_VIRTUAL_PYTHONHOME
Expand Down Expand Up @@ -77,6 +82,9 @@ deactivate nondestructive

set -gx VIRTUAL_ENV __VIRTUAL_ENV__

set -gx _OLD_PKG_CONFIG_PATH "$PKG_CONFIG_PATH"
set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig:$PKG_CONFIG_PATH"

# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
if test (string sub -s 1 -l 1 $FISH_VERSION) -lt 3
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
Expand Down
4 changes: 3 additions & 1 deletion src/virtualenv/activation/nushell/activate.nu
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ export-env {
} else {
__VIRTUAL_PROMPT__
}
let new_env = { $path_name: $new_path VIRTUAL_ENV: $virtual_env VIRTUAL_ENV_PROMPT: $virtual_env_prompt }
let old_pkg_config_path = if (has-env 'PKG_CONFIG_PATH') { $env.PKG_CONFIG_PATH } else { '' }
let new_pkg_config_path = $'($virtual_env)/lib/pkgconfig:($old_pkg_config_path)'
let new_env = { $path_name: $new_path VIRTUAL_ENV: $virtual_env VIRTUAL_ENV_PROMPT: $virtual_env_prompt PKG_CONFIG_PATH: $new_pkg_config_path }
if (has-env 'TCL_LIBRARY') {
let $new_env = $new_env | insert TCL_LIBRARY __TCL_LIBRARY__
}
Expand Down
10 changes: 10 additions & 0 deletions src/virtualenv/activation/powershell/activate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ function global:deactivate([switch] $NonDestructive) {
}
}

if (Test-Path variable:_OLD_PKG_CONFIG_PATH) {
$env:PKG_CONFIG_PATH = $variable:_OLD_PKG_CONFIG_PATH
Remove-Variable "_OLD_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 @@ -78,6 +83,11 @@ if (__TK_LIBRARY__ -ne "") {

New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH

if (Test-Path env:PKG_CONFIG_PATH) {
New-Variable -Scope global -Name _OLD_PKG_CONFIG_PATH -Value $env:PKG_CONFIG_PATH
}
$env:PKG_CONFIG_PATH = "$env:VIRTUAL_ENV\lib\pkgconfig;$env:PKG_CONFIG_PATH"

$env:PATH = "$env:VIRTUAL_ENV/" + __BIN_NAME__ + __PATH_SEP__ + $env:PATH
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
function global:_old_virtual_prompt {
Expand Down
8 changes: 8 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,14 @@
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
os.environ["VIRTUAL_ENV_PROMPT"] = __VIRTUAL_PROMPT__ or os.path.basename(base)

# Set PKG_CONFIG_PATH to include the virtualenv's pkgconfig directory
pkg_config_path = os.path.join(base, "lib", "pkgconfig")
existing_pkg_config_path = os.environ.get("PKG_CONFIG_PATH", "")
if existing_pkg_config_path:
os.environ["PKG_CONFIG_PATH"] = os.pathsep.join([pkg_config_path, existing_pkg_config_path])
else:
os.environ["PKG_CONFIG_PATH"] = pkg_config_path

# 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
177 changes: 177 additions & 0 deletions tests/unit/activation/test_pkg_config_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"""Test that PKG_CONFIG_PATH is properly set and restored during activation/deactivation."""
Comment thread
gaborbernat marked this conversation as resolved.
Outdated

from __future__ import annotations

from argparse import Namespace

import pytest

from virtualenv.activation import (
BashActivator,
BatchActivator,
CShellActivator,
FishActivator,
NushellActivator,
PowerShellActivator,
PythonActivator,
)
from virtualenv.info import IS_WIN


def _create_test_env(tmp_path):
"""Helper to create a mock virtualenv environment for testing."""

class MockInterpreter:
tcl_lib = None
tk_lib = None

class MockCreator:
def __init__(self, dest):
self.dest = dest
self.bin_dir = dest / ("Scripts" if IS_WIN else "bin")
self.bin_dir.mkdir(parents=True)
self.interpreter = MockInterpreter()
self.pyenv_cfg = {}
self.env_name = "test-env"

return MockCreator(tmp_path)


@pytest.mark.parametrize(
("activator_class", "script_name", "search_patterns"),
[
(
BashActivator,
"activate",
{
"save": '_OLD_PKG_CONFIG_PATH="${PKG_CONFIG_PATH}"',
"set": 'PKG_CONFIG_PATH="${VIRTUAL_ENV}/lib/pkgconfig:${PKG_CONFIG_PATH}"',
"export": "export PKG_CONFIG_PATH",
"restore": 'PKG_CONFIG_PATH="$_OLD_PKG_CONFIG_PATH"',
"unset": "unset _OLD_PKG_CONFIG_PATH",
},
),
(
FishActivator,
"activate.fish",
{
"save": "set -gx _OLD_PKG_CONFIG_PATH",
"set": 'set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig:$PKG_CONFIG_PATH"',
"restore": "set -gx PKG_CONFIG_PATH",
"erase": "set -e _OLD_PKG_CONFIG_PATH",
},
),
(
CShellActivator,
"activate.csh",
{
"save": 'set _OLD_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"',
"set_with_existing": 'setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig:${PKG_CONFIG_PATH}"',
"set_without_existing": 'setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig"',
"deactivate_test": "test $?_OLD_PKG_CONFIG_PATH != 0",
"restore": 'setenv PKG_CONFIG_PATH "$_OLD_PKG_CONFIG_PATH:q"',
"unset": "unset _OLD_PKG_CONFIG_PATH",
},
),
],
)
def test_pkg_config_path_unix_shells(tmp_path, activator_class, script_name, search_patterns):
"""Test PKG_CONFIG_PATH is set for Unix-like shells."""
if IS_WIN and activator_class in {BashActivator, FishActivator, CShellActivator}:
pytest.skip("Unix shell test on Windows")

creator = _create_test_env(tmp_path)
options = Namespace(prompt=None)
activator = activator_class(options)

# Generate the activation script
activator.generate(creator)

# Read the generated script
script_path = creator.bin_dir / script_name
content = script_path.read_text(encoding="utf-8")

# Verify all expected patterns are present
for pattern_name, pattern in search_patterns.items():
assert pattern in content, f"Missing {pattern_name} pattern: {pattern}\nScript content:\n{content}"


@pytest.mark.skipif(not IS_WIN, reason="Windows-only test")
@pytest.mark.parametrize(
("activator_class", "script_name", "search_patterns"),
[
(
BatchActivator,
"activate.bat",
{
"save": 'set "_OLD_PKG_CONFIG_PATH=%PKG_CONFIG_PATH%"',
"set": 'set "PKG_CONFIG_PATH=%VIRTUAL_ENV%\\lib\\pkgconfig;%PKG_CONFIG_PATH%"',
},
),
(
PowerShellActivator,
"activate.ps1",
{
"save": "New-Variable -Scope global -Name _OLD_PKG_CONFIG_PATH",
"set": '$env:PKG_CONFIG_PATH = "$env:VIRTUAL_ENV\\lib\\pkgconfig;$env:PKG_CONFIG_PATH"',
"restore": "$env:PKG_CONFIG_PATH = $variable:_OLD_PKG_CONFIG_PATH",
"remove": 'Remove-Variable "_OLD_PKG_CONFIG_PATH" -Scope global',
},
),
],
)
def test_pkg_config_path_windows_shells(tmp_path, activator_class, script_name, search_patterns):
"""Test PKG_CONFIG_PATH is set for Windows shells."""
creator = _create_test_env(tmp_path)
options = Namespace(prompt=None)
activator = activator_class(options)

# Generate the activation script
activator.generate(creator)

# Read the generated script
script_path = creator.bin_dir / script_name
content = script_path.read_text(encoding="utf-8")

# Verify all expected patterns are present
for pattern_name, pattern in search_patterns.items():
assert pattern in content, f"Missing {pattern_name} pattern: {pattern}\nScript content:\n{content}"


def test_pkg_config_path_python_activator(tmp_path):
"""Test PKG_CONFIG_PATH is set in Python activator."""
creator = _create_test_env(tmp_path)
# Add libs attribute needed by PythonActivator
creator.libs = [tmp_path / "Lib" / "site-packages"]
options = Namespace(prompt=None)
activator = PythonActivator(options)

# Generate the activation script
activator.generate(creator)

# Read the generated script
script_path = creator.bin_dir / "activate_this.py"
content = script_path.read_text(encoding="utf-8")

# Verify PKG_CONFIG_PATH is handled
assert "PKG_CONFIG_PATH" in content, f"PKG_CONFIG_PATH not found in activate_this.py:\n{content}"
assert "pkg_config_path" in content, f"pkg_config_path variable not found in activate_this.py:\n{content}"


def test_pkg_config_path_nushell(tmp_path):
"""Test PKG_CONFIG_PATH is set in Nushell."""
creator = _create_test_env(tmp_path)
options = Namespace(prompt=None)
activator = NushellActivator(options)

# Generate the activation script
activator.generate(creator)

# Read the generated script
script_path = creator.bin_dir / "activate.nu"
content = script_path.read_text(encoding="utf-8")

# Verify PKG_CONFIG_PATH is handled
assert "PKG_CONFIG_PATH" in content, f"PKG_CONFIG_PATH not found in activate.nu:\n{content}"
assert "old_pkg_config_path" in content, f"old_pkg_config_path variable not saved in activate.nu:\n{content}"
assert "new_pkg_config_path" in content, f"new_pkg_config_path variable not found in activate.nu:\n{content}"
Loading