diff --git a/docs/changelog/2637.feature.rst b/docs/changelog/2637.feature.rst new file mode 100644 index 000000000..f4d0a35ee --- /dev/null +++ b/docs/changelog/2637.feature.rst @@ -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`. diff --git a/src/virtualenv/activation/bash/activate.sh b/src/virtualenv/activation/bash/activate.sh index de2d2422c..856843474 100644 --- a/src/virtualenv/activation/bash/activate.sh +++ b/src/virtualenv/activation/bash/activate.sh @@ -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 @@ -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 diff --git a/src/virtualenv/activation/batch/activate.bat b/src/virtualenv/activation/batch/activate.bat index 62f393c80..06192921f 100644 --- a/src/virtualenv/activation/batch/activate.bat +++ b/src/virtualenv/activation/batch/activate.bat @@ -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%" diff --git a/src/virtualenv/activation/batch/deactivate.bat b/src/virtualenv/activation/batch/deactivate.bat index 7a12d47ed..d486958ad 100644 --- a/src/virtualenv/activation/batch/deactivate.bat +++ b/src/virtualenv/activation/batch/deactivate.bat @@ -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= diff --git a/src/virtualenv/activation/cshell/activate.csh b/src/virtualenv/activation/cshell/activate.csh index 5c02616d7..ae6fbedd8 100644 --- a/src/virtualenv/activation/cshell/activate.csh +++ b/src/virtualenv/activation/cshell/activate.csh @@ -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 @@ -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" diff --git a/src/virtualenv/activation/fish/activate.fish b/src/virtualenv/activation/fish/activate.fish index c9d174997..b0dfc4ae0 100644 --- a/src/virtualenv/activation/fish/activate.fish +++ b/src/virtualenv/activation/fish/activate.fish @@ -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 @@ -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) diff --git a/src/virtualenv/activation/nushell/activate.nu b/src/virtualenv/activation/nushell/activate.nu index b48fdd03f..7046c5880 100644 --- a/src/virtualenv/activation/nushell/activate.nu +++ b/src/virtualenv/activation/nushell/activate.nu @@ -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__ } diff --git a/src/virtualenv/activation/powershell/activate.ps1 b/src/virtualenv/activation/powershell/activate.ps1 index 052a8298d..51878f88f 100644 --- a/src/virtualenv/activation/powershell/activate.ps1 +++ b/src/virtualenv/activation/powershell/activate.ps1 @@ -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 @@ -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 { diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py index 9cc816fab..43dc98a04 100644 --- a/src/virtualenv/activation/python/activate_this.py +++ b/src/virtualenv/activation/python/activate_this.py @@ -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): diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py index 0a9c588cf..611c56a5b 100644 --- a/tests/unit/activation/test_bash.py +++ b/tests/unit/activation/test_bash.py @@ -46,6 +46,13 @@ def __init__(self, dest): # The teardown logic is always present in deactivate() assert "unset _OLD_VIRTUAL_TCL_LIBRARY" in content assert "unset _OLD_VIRTUAL_TK_LIBRARY" in content + assert "unset _OLD_PKG_CONFIG_PATH" in content + + # PKG_CONFIG_PATH is always set + assert '_OLD_PKG_CONFIG_PATH="${PKG_CONFIG_PATH}"' in content + assert 'PKG_CONFIG_PATH="${VIRTUAL_ENV}/lib/pkgconfig:${PKG_CONFIG_PATH}"' in content + assert "export PKG_CONFIG_PATH" in content + assert 'PKG_CONFIG_PATH="$_OLD_PKG_CONFIG_PATH"' in content if present: assert 'if [ /path/to/tcl != "" ]; then' in content diff --git a/tests/unit/activation/test_batch.py b/tests/unit/activation/test_batch.py index 74c5a0f97..468bcff91 100644 --- a/tests/unit/activation/test_batch.py +++ b/tests/unit/activation/test_batch.py @@ -74,6 +74,13 @@ def __init__(self, dest): deactivate_content = (creator.bin_dir / "deactivate.bat").read_text(encoding="utf-8") # THEN + # PKG_CONFIG_PATH is always set + assert '@if defined PKG_CONFIG_PATH @set "_OLD_PKG_CONFIG_PATH=%PKG_CONFIG_PATH%"' in activate_content + assert '@set "PKG_CONFIG_PATH=%VIRTUAL_ENV%\\lib\\pkgconfig;%PKG_CONFIG_PATH%"' in activate_content + assert '@if defined _OLD_PKG_CONFIG_PATH @set "PKG_CONFIG_PATH=%_OLD_PKG_CONFIG_PATH%"' in deactivate_content + assert "@if not defined _OLD_PKG_CONFIG_PATH @set PKG_CONFIG_PATH=" in deactivate_content + assert "@set _OLD_PKG_CONFIG_PATH=" in deactivate_content + if present: assert '@if NOT "C:\\tcl"=="" @set "TCL_LIBRARY=C:\\tcl"' in activate_content assert '@if NOT "C:\\tk"=="" @set "TK_LIBRARY=C:\\tk"' in activate_content diff --git a/tests/unit/activation/test_csh.py b/tests/unit/activation/test_csh.py index 5cea684ec..97096e192 100644 --- a/tests/unit/activation/test_csh.py +++ b/tests/unit/activation/test_csh.py @@ -44,6 +44,14 @@ def __init__(self, dest): activator.generate(creator) content = (creator.bin_dir / "activate.csh").read_text(encoding="utf-8") + # PKG_CONFIG_PATH is always set + assert "test $?_OLD_PKG_CONFIG_PATH != 0" in content + assert 'set _OLD_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"' in content + assert 'setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig:${PKG_CONFIG_PATH}"' in content + assert 'setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig"' in content + assert 'setenv PKG_CONFIG_PATH "$_OLD_PKG_CONFIG_PATH:q"' in content + assert "unset _OLD_PKG_CONFIG_PATH" in content + if present: assert "test $?_OLD_VIRTUAL_TCL_LIBRARY != 0" in content assert "test $?_OLD_VIRTUAL_TK_LIBRARY != 0" in content diff --git a/tests/unit/activation/test_fish.py b/tests/unit/activation/test_fish.py index c15a8f513..2aefcb710 100644 --- a/tests/unit/activation/test_fish.py +++ b/tests/unit/activation/test_fish.py @@ -44,6 +44,11 @@ def __init__(self, dest): content = (creator.bin_dir / "activate.fish").read_text(encoding="utf-8") # THEN + # PKG_CONFIG_PATH is always set + assert 'set -gx _OLD_PKG_CONFIG_PATH "$PKG_CONFIG_PATH"' in content + assert 'set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig:$PKG_CONFIG_PATH"' in content + assert "set -e _OLD_PKG_CONFIG_PATH" in content + if present: assert "set -gx TCL_LIBRARY '/path/to/tcl'" in content assert "set -gx TK_LIBRARY '/path/to/tk'" in content diff --git a/tests/unit/activation/test_nushell.py b/tests/unit/activation/test_nushell.py index 08c5cb1a1..82340053c 100644 --- a/tests/unit/activation/test_nushell.py +++ b/tests/unit/activation/test_nushell.py @@ -36,6 +36,11 @@ def __init__(self, dest): content = (creator.bin_dir / "activate.nu").read_text(encoding="utf-8") # THEN + # PKG_CONFIG_PATH is always set + assert "let old_pkg_config_path = if (has-env 'PKG_CONFIG_PATH')" in content + assert "let new_pkg_config_path = " in content + assert "PKG_CONFIG_PATH: $new_pkg_config_path" in content + expected_tcl = f"let $new_env = $new_env | insert TCL_LIBRARY {quoted_tcl_path}" expected_tk = f"let $new_env = $new_env | insert TK_LIBRARY {quoted_tk_path}" diff --git a/tests/unit/activation/test_powershell.py b/tests/unit/activation/test_powershell.py index cd6057eec..c6a4c687c 100644 --- a/tests/unit/activation/test_powershell.py +++ b/tests/unit/activation/test_powershell.py @@ -76,6 +76,13 @@ def __init__(self, dest): content = (creator.bin_dir / "activate.ps1").read_text(encoding="utf-8-sig") # THEN + # PKG_CONFIG_PATH is always set + assert "New-Variable -Scope global -Name _OLD_PKG_CONFIG_PATH" in content + assert '$env:PKG_CONFIG_PATH = "$env:VIRTUAL_ENV\\lib\\pkgconfig;$env:PKG_CONFIG_PATH"' in content + assert "if (Test-Path variable:_OLD_PKG_CONFIG_PATH)" in content + assert "$env:PKG_CONFIG_PATH = $variable:_OLD_PKG_CONFIG_PATH" in content + assert 'Remove-Variable "_OLD_PKG_CONFIG_PATH" -Scope global' in content + if present: assert "if ('C:\\tcl' -ne \"\")" in content assert "$env:TCL_LIBRARY = 'C:\\tcl'" in content diff --git a/tests/unit/activation/test_python_activator.py b/tests/unit/activation/test_python_activator.py index 24a3561c5..09f27da47 100644 --- a/tests/unit/activation/test_python_activator.py +++ b/tests/unit/activation/test_python_activator.py @@ -2,6 +2,7 @@ import os import sys +from argparse import Namespace from ast import literal_eval from textwrap import dedent @@ -9,6 +10,39 @@ from virtualenv.info import IS_WIN +def test_python_activator_generates_pkg_config_path(tmp_path): + """Test that activate_this.py sets PKG_CONFIG_PATH.""" + + 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.libs = [dest / "Lib" / "site-packages"] + self.env_name = "test-env" + self.interpreter = MockInterpreter() + self.pyenv_cfg = {} + + creator = MockCreator(tmp_path) + options = Namespace(prompt=None) + activator = PythonActivator(options) + + # Generate the activation script + activator.generate(creator) + + # Read the generated script + content = (creator.bin_dir / "activate_this.py").read_text(encoding="utf-8") + + # Verify PKG_CONFIG_PATH is set + assert "PKG_CONFIG_PATH" in content + assert "pkg_config_path" in content + assert 'os.path.join(base, "lib", "pkgconfig")' in content + + def test_python(raise_on_non_source_class, activation_tester): class Python(raise_on_non_source_class): def __init__(self, session) -> None: