4

I'm writing a build script to resolve dependent shared libraries (and their shared libraries, etc.). These shared libraries do not exist in the normal PATH environment variable.

For the build process to work (for the compiler to find these libraries), the PATH has been changed to include the directories of these libraries.

The build process is thus:

Loader script (changes PATH) -> Python-based build script -> Configure -> Build -> Resolve Dependencies -> Install.

The Python instance inherits a changed PATH variable from its parent shell.

From within Python, I'm trying to get the default PATH (not the one inherited from its parent shell).

The idea:

The idea to resolve the 'default' PATH variable is to somehow 'signal' the OS to start a new process (running a script that prints PATH) but this process is NOT a child of the current Python process (and presumably won't inherit its modified environment variables).

The attempted implementation:

import os
import sys

print os.environ["PATH"]
print "---"
os.spawnl(os.P_WAIT, sys.executable, "python", "-c \"import os;print(os.environ['PATH']);\"")

os.spawn appears to use the same environment variables as the Python process which calls it. I've also tried this approach with subprocess.POpen, with no success.

Can this approach be implemented ? If not, what is an alternative approach (given that the loader script and the overall process can't change)?

I'm currently using Windows but the build script is to be cross-platform.

EDIT:

The cross-platform constraint appears to be too restrictive. Different implementations of the same concept can now be considered.

As an example, using code from this answer, the Windows registry can be used to get the 'default' system PATH variable.

try:
    import _winreg as winreg
except ImportError:
    try:
        import winreg
    except ImportError:
        winreg = None

def env_keys(user=True):
    if user:
        root = winreg.HKEY_CURRENT_USER
        subkey = "Environment"
    else:
        root = winreg.HKEY_LOCAL_MACHINE
        subkey = r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
    return root, subkey

def get_env(name, user=True):
    root, subkey = env_keys(user)
    key = winreg.OpenKey(root, subkey, 0, winreg.KEY_READ)
    try:
        value, _ = winreg.QueryValueEx(key, name)
    except WindowsError:
        return ""
    value = winreg.ExpandEnvironmentStrings(value)
    return value

print get_env("PATH", False)

A consistent approach for *nix is needed.

Community
  • 1
  • 1
dilbert
  • 3,008
  • 1
  • 25
  • 34

2 Answers2

5

Using subprocess.Popen, you can provide an environment for the child process to use:

default_path = os.environ['PATH'] # save the default path before changing it
os.environ['PATH'] = # whatever you want
child_env = os.environ.copy()
child_env['PATH'] = default_path
# change env
subprocess.Popen(..., env=child_env)

The documentation states that the provided environment will be used instead of inheriting it from the parent:

If env is not None, it must be a mapping that defines the environment variables for the new process; these are used instead of inheriting the current process’ environment, which is the default behavior.

dano
  • 91,354
  • 19
  • 222
  • 219
  • The python build script isn't to change the `PATH` variable as there are other non-python tools which need the modified `PATH`. So the `PATH` changing must occur in the loading script. – dilbert May 19 '14 at 01:22
  • I edited my answer to reflect what I think you're trying to do, but I'm really not sure I fully understand the question. – dano May 19 '14 at 01:46
  • Ah wait, is the loader script not written in Python? Couldn't you just have the loader script save the default path to an environment variable before calling the Python script? – dano May 19 '14 at 01:48
  • my apologies for any confusion. The loader script is a `.bat` file and must remain so. I'd edited the question. Do you know of a way to 'signal' to the OS to start a new process that isn't a child process of Python? – dilbert May 19 '14 at 01:50
  • The python script is to be portable, but the loader script is platform specific. I don't want to put any references in the build script to a backup PATH. – dilbert May 19 '14 at 01:52
0

What do you really mean by 'default' value of PATH? The value it had when you logged in? Some system-wide default? The value the loader script started with before it made changes?

The easiest thing would be to wrap the loader script (if you really cannot change it) with one of your own that saves the current value of PATH in some other environment variable like OLD_PATH. Then you can use something like:

os.spawnle( ... , {'PATH' : os.environ['OLD_PATH']})

Or you could spawn a shell as a login or at least interactive shell and let it source the user's .bashrc (or other startup) before invoking python.

** update ** for windows, and assuming you just want to get the PATH:

Spawn CMD.EXE, have it execute command 'echo %PATH%'

BobHy
  • 1,575
  • 10
  • 23
  • The default in this instance is considered to be the environment prior to the loader script. I'm not looking to pass an environment variable to a subprocess; I'm looking for the subprocess to return a default environment. This is on Windows although the build script is to be cross-platform. – dilbert May 19 '14 at 01:16
  • I see you'd like the spawned process to tell you what its environment was. So then the second part of my answer come in: spawn a process which is running CMD.EXE (windows) or bash (linux), and let it tell you what its PATH is. Updating answer for these particulars. – BobHy May 19 '14 at 01:23
  • I thought about that. Is there are way to 'signal' to the OS to start a new process (but not as a child of the current Python process)? – dilbert May 19 '14 at 01:25