Spaces:
Build error
Build error
| # (c) Anaconda, Inc. / https://anaconda.com | |
| # All Rights Reserved | |
| # This file is under the BSD license | |
| # | |
| # Helper script for adding and removing entries in the | |
| # Windows system path from the NSIS installer. | |
| __all__ = ['remove_from_system_path', 'add_to_system_path', | |
| 'broadcast_environment_settings_change'] | |
| import ctypes | |
| import os | |
| import re | |
| import sys | |
| from ctypes import wintypes | |
| from os import path | |
| if sys.version_info[0] >= 3: | |
| import winreg as reg | |
| else: | |
| import _winreg as reg | |
| # If pythonw is being run, there may be no write function | |
| if sys.stdout and sys.stdout.write: | |
| out = sys.stdout.write | |
| err = sys.stderr.write | |
| else: | |
| OutputDebugString = ctypes.windll.kernel32.OutputDebugStringW | |
| OutputDebugString.argtypes = [ctypes.c_wchar_p] | |
| def out(x): | |
| OutputDebugString('_nsis.py: ' + x) | |
| def err(x): | |
| OutputDebugString('_nsis.py: Error: ' + x) | |
| HWND_BROADCAST = 0xffff | |
| WM_SETTINGCHANGE = 0x001A | |
| SMTO_ABORTIFHUNG = 0x0002 | |
| SendMessageTimeout = ctypes.windll.user32.SendMessageTimeoutW | |
| SendMessageTimeout.restype = None # wintypes.LRESULT | |
| SendMessageTimeout.argtypes = [wintypes.HWND, wintypes.UINT, wintypes.WPARAM, | |
| wintypes.LPCWSTR, wintypes.UINT, wintypes.UINT, | |
| ctypes.POINTER(wintypes.DWORD)] | |
| def sz_expand(value, value_type): | |
| if value_type == reg.REG_EXPAND_SZ: | |
| return reg.ExpandEnvironmentStrings(value) | |
| else: | |
| return value | |
| def remove_from_system_path(pathname, allusers=True, path_env_var='PATH'): | |
| """Removes all entries from the path which match the value in 'pathname' | |
| You must call broadcast_environment_settings_change() after you are finished | |
| manipulating the environment with this and other functions. | |
| For example, | |
| # Remove Anaconda from PATH | |
| remove_from_system_path('C:\\Anaconda') | |
| broadcast_environment_settings_change() | |
| """ | |
| pathname = path.normcase(path.normpath(pathname)) | |
| envkeys = [(reg.HKEY_CURRENT_USER, r'Environment')] | |
| if allusers: | |
| envkeys.append((reg.HKEY_LOCAL_MACHINE, | |
| r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment')) | |
| for root, keyname in envkeys: | |
| key = reg.OpenKey(root, keyname, 0, | |
| reg.KEY_QUERY_VALUE | reg.KEY_SET_VALUE) | |
| reg_value = None | |
| try: | |
| reg_value = reg.QueryValueEx(key, path_env_var) | |
| except WindowsError: | |
| # This will happen if we're a non-admin install and the user has | |
| # no PATH variable. | |
| reg.CloseKey(key) | |
| continue | |
| try: | |
| any_change = False | |
| results = [] | |
| for v in reg_value[0].split(os.pathsep): | |
| vexp = sz_expand(v, reg_value[1]) | |
| # Check if the expanded path matches the | |
| # requested path in a normalized way | |
| if path.normcase(path.normpath(vexp)) == pathname: | |
| any_change = True | |
| else: | |
| # Append the original unexpanded version to the results | |
| results.append(v) | |
| modified_path = os.pathsep.join(results) | |
| if any_change: | |
| reg.SetValueEx(key, path_env_var, 0, reg_value[1], modified_path) | |
| except Exception: | |
| # If there's an error (e.g. when there is no PATH for the current | |
| # user), continue on to try the next root/keyname pair | |
| reg.CloseKey(key) | |
| def add_to_system_path(paths, allusers=True, path_env_var='PATH'): | |
| """Adds the requested paths to the system PATH variable. | |
| You must call broadcast_environment_settings_change() after you are finished | |
| manipulating the environment with this and other functions. | |
| """ | |
| # Make sure it's a list | |
| if not issubclass(type(paths), list): | |
| paths = [paths] | |
| # Ensure all the paths are valid before we start messing with the | |
| # registry. | |
| new_paths = None | |
| for p in paths: | |
| p = path.abspath(p) | |
| if new_paths: | |
| new_paths = new_paths + os.pathsep + p | |
| else: | |
| new_paths = p | |
| if allusers: | |
| # All Users | |
| root, keyname = (reg.HKEY_LOCAL_MACHINE, | |
| r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment') | |
| else: | |
| # Just Me | |
| root, keyname = (reg.HKEY_CURRENT_USER, r'Environment') | |
| key = reg.OpenKey(root, keyname, 0, | |
| reg.KEY_QUERY_VALUE | reg.KEY_SET_VALUE) | |
| reg_type = None | |
| reg_value = None | |
| try: | |
| try: | |
| reg_value = reg.QueryValueEx(key, path_env_var) | |
| except WindowsError: | |
| # This will happen if we're a non-admin install and the user has | |
| # no PATH variable; in which case, we can write our new paths | |
| # directly. | |
| reg_type = reg.REG_EXPAND_SZ | |
| final_value = new_paths | |
| else: | |
| # Put to the front of PATH irrespective of allusers. The old | |
| # behaviour was asking for trouble and did not, contrary to what | |
| # this comment used to say, mirror what happens on *nix. | |
| reg_type = reg_value[1] | |
| final_value = new_paths + os.pathsep + reg_value[0] | |
| # Replace coincident ';' with a single ';' | |
| final_value = re.sub(r'([\;])+', r'\1', final_value) | |
| # Remove trailing ';' | |
| final_value = re.sub(r'\;$', '', final_value) | |
| # Remove any '"', they are not needed and break conda. | |
| final_value = final_value.replace('"', '') | |
| # Warn about directories that do not exist. | |
| directories = final_value.split(';') | |
| for directory in directories: | |
| if '%' not in directory and not os.path.exists(directory): | |
| out("WARNING: Old PATH entry '%s' does not exist\n" % (directory)) | |
| reg.SetValueEx(key, path_env_var, 0, reg_type, final_value) | |
| finally: | |
| reg.CloseKey(key) | |
| def _reg_query_sub_keys(handle, key, keylist=[]): | |
| reghandle = reg.OpenKey(handle, key, 0, reg.KEY_READ) | |
| try: | |
| i = 0 | |
| while True: | |
| subkey = reg.EnumKey(reghandle, i) | |
| i += 1 | |
| _reg_query_sub_keys(handle, key + subkey + "\\", keylist) | |
| except WindowsError as ex: | |
| if ex.winerror == 259: | |
| keylist.append(key) | |
| finally: | |
| reg.CloseKey(reghandle) | |
| def get_previous_install_prefixes(pyversion, arch, allusers=True): | |
| """Returns a list of prefixes for all old installations of this arch so that | |
| they can be removed from PATH if present. Note, it would be preferable to | |
| uninstall them properly instead. | |
| """ | |
| if allusers: | |
| # All Users | |
| key, subkey = (reg.HKEY_LOCAL_MACHINE, | |
| r'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\') | |
| else: | |
| # Just Me | |
| key, subkey = (reg.HKEY_CURRENT_USER, | |
| r'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\') | |
| keylist = [] | |
| # We ignore pyversion and instead look for any *conda installations. | |
| regex = re.compile(r'Python \S+ \(\S+conda[0-9]+ \S+ '+arch+r'\)') | |
| _reg_query_sub_keys(key, subkey, keylist) | |
| results = [] | |
| for uninstsubkey in keylist: | |
| final_part = os.path.basename(uninstsubkey.rstrip('\\')) | |
| if regex.match(final_part): | |
| try: | |
| with reg.OpenKeyEx(key, uninstsubkey, 0, | |
| reg.KEY_QUERY_VALUE) as keyhandle: | |
| reg_value = reg.QueryValueEx(keyhandle, 'UninstallString') | |
| results.append(os.path.dirname(re.sub(r'^"|"$', '', reg_value[0]))) | |
| except Exception: | |
| pass | |
| return results | |
| def broadcast_environment_settings_change(): | |
| """Broadcasts to the system indicating that master environment variables have changed. | |
| This must be called after using the other functions in this module to | |
| manipulate environment variables. | |
| """ | |
| SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, u'Environment', | |
| SMTO_ABORTIFHUNG, 5000, ctypes.pointer(wintypes.DWORD())) | |