Fix environment variables to be consistently set when using --system or --permanent (#515)

Also switch PostMessage to SendMessageWithTimeout and only send one if
environment variables actually changed.

Also, enable and fix --permanent unit tests.
This commit is contained in:
Chris Savoie
2020-10-09 12:58:59 -07:00
committed by GitHub
parent 107dc62dd7
commit 4fe6e492c0
3 changed files with 113 additions and 92 deletions

180
emsdk.py
View File

@@ -299,6 +299,7 @@ def remove_tree(d):
def win_set_environment_variable_direct(key, value, system=True):
folder = None
try:
if system:
# Read globally from ALL USERS section.
@@ -308,23 +309,22 @@ def win_set_environment_variable_direct(key, value, system=True):
folder = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS)
winreg.SetValueEx(folder, key, 0, winreg.REG_EXPAND_SZ, value)
debug_print('Set key=' + key + ' with value ' + value + ' in registry.')
return True
except Exception as e:
# 'Access is denied.'
if e.args[0] == 5:
if e.args[3] == 5:
exit_with_error('Error! Failed to set the environment variable \'' + key + '\'! Setting environment variables permanently requires administrator access. Please rerun this command with administrative privileges. This can be done for example by holding down the Ctrl and Shift keys while opening a command prompt in start menu.')
errlog('Failed to write environment variable ' + key + ':')
errlog(str(e))
folder.Close()
return None
folder.Close()
HWND_BROADCAST = ctypes.wintypes.HWND(0xFFFF) # win32con.HWND_BROADCAST == 65535
WM_SETTINGCHANGE = 0x001A # win32con.WM_SETTINGCHANGE == 26
ctypes.windll.user32.SendMessageA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 'Environment')
return False
finally:
if folder is not None:
folder.Close()
def win_get_environment_variable(key, system=True):
def win_get_environment_variable(key, system=True, fallback=True):
try:
folder = None
try:
if system:
# Read globally from ALL USERS section.
@@ -334,22 +334,24 @@ def win_get_environment_variable(key, system=True):
folder = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment')
value = str(winreg.QueryValueEx(folder, key)[0])
except Exception:
# PyWin32 is not available - read via os.environ. This has the drawback
# If reading registry fails for some reason - read via os.environ. This has the drawback
# that expansion items such as %PROGRAMFILES% will have been expanded, so
# need to be precise not to set these back to system registry, or
# expansion items would be lost.
return os.environ[key]
if fallback:
return os.environ[key]
return None
finally:
if folder is not None:
folder.Close()
except Exception as e:
# this catch is if both the registry key threw an exception and the key is not in os.environ
if e.args[0] != 2:
# 'The system cannot find the file specified.'
errlog('Failed to read environment variable ' + key + ':')
errlog(str(e))
try:
folder.Close()
except Exception:
pass
return None
folder.Close()
return value
@@ -367,11 +369,11 @@ def win_get_active_environment_variable(key):
def win_set_environment_variable(key, value, system=True):
debug_print('set ' + str(key) + '=' + str(value) + ', in system=' + str(system), file=sys.stderr)
previous_value = win_get_environment_variable(key, system)
previous_value = win_get_environment_variable(key, system, fallback=False)
if previous_value == value:
debug_print(' no need to set, since same value already exists.')
# No need to elevate UAC for nothing to set the same value, skip.
return
return False
if not value:
try:
@@ -382,13 +384,12 @@ def win_set_environment_variable(key, value, system=True):
debug_print(str(cmd))
value = subprocess.call(cmd, stdout=subprocess.PIPE)
except Exception:
return
return
return False
return True
try:
if system:
win_set_environment_variable_direct(key, value, system)
return
if win_set_environment_variable_direct(key, value, system):
return True
# Escape % signs so that we don't expand references to environment variables.
value = value.replace('%', '^%')
if len(value) >= 1024:
@@ -398,15 +399,53 @@ def win_set_environment_variable(key, value, system=True):
retcode = subprocess.call(cmd, stdout=subprocess.PIPE)
if retcode != 0:
errlog('ERROR! Failed to set environment variable ' + key + '=' + value + '. You may need to set it manually.')
else:
return True
except Exception as e:
errlog('ERROR! Failed to set environment variable ' + key + '=' + value + ':')
errlog(str(e))
errlog('You may need to set it manually.')
return False
def win_set_environment_variables(env_vars_to_add, system):
if not env_vars_to_add:
return
changed = False
for key, value in env_vars_to_add:
if win_set_environment_variable(key, value, system):
if not changed:
changed = True
print('Setting global environment variables:')
print(key + ' = ' + value)
if not changed:
print('Global environment variables up to date')
return
# if changes were made then we need to notify other processes
try:
HWND_BROADCAST = ctypes.wintypes.HWND(0xFFFF) # win32con.HWND_BROADCAST == 65535
WM_SETTINGCHANGE = 0x001A # win32con.WM_SETTINGCHANGE == 26
SMTO_BLOCK = 0x0001 # win32con.SMTO_BLOCK == 1
ctypes.windll.user32.SendMessageTimeoutA(
HWND_BROADCAST, # hWnd: notify everyone
WM_SETTINGCHANGE, # Msg: registry changed
0, # wParam: Must be 0 when setting changed is sent by users
'Environment', # lParam: Specifically environment variables changed
SMTO_BLOCK, # fuFlags: Wait for message to be sent or timeout
100) # uTimeout: 100ms
except Exception as e:
errlog('SendMessageTimeout failed with error: ' + str(e))
def win_delete_environment_variable(key, system=True):
debug_print('win_delete_environment_variable(key=' + key + ', system=' + str(system) + ')')
win_set_environment_variable(key, None, system)
return win_set_environment_variable(key, None, system)
# Returns the absolute pathname to the given path inside the Emscripten SDK.
@@ -1770,14 +1809,6 @@ class Tool(object):
return False
return True
def win_activate_env_vars(self, system):
if WINDOWS:
envs = self.activated_environment()
for env in envs:
key, value = parse_key_value(env)
win_set_environment_variable(key, value, system)
# If this tool can be installed on this system, this function returns True.
# Otherwise, this function returns a string that describes the reason why this
# tool is not available.
@@ -2416,20 +2447,14 @@ def set_active_tools(tools_to_activate, permanently_activate, system):
# calling shell environment. On other platform `source emsdk_env.sh` is
# required.
if WINDOWS:
env_string = construct_env(tools_to_activate)
# always set local environment variables since permanently activating will only set the registry settings and
# will not affect the current session
env_vars_to_add = get_env_vars_to_add(tools_to_activate)
env_string = construct_env_with_vars(env_vars_to_add)
write_set_env_script(env_string)
# Apply environment variables to global all users section.
if WINDOWS and permanently_activate:
# Individual env. vars
for tool in tools_to_activate:
tool.win_activate_env_vars(system=system)
# PATH variable
newpath, added_items = adjusted_path(tools_to_activate, system=system)
# Are there any actual changes?
if newpath != os.environ['PATH']:
win_set_environment_variable('PATH', newpath, system=system)
if permanently_activate:
win_set_environment_variables(env_vars_to_add, system)
return tools_to_activate
@@ -2504,22 +2529,14 @@ def adjusted_path(tools_to_activate, system=False):
return (separator.join(whole_path), new_emsdk_tools)
def construct_env(tools_to_activate):
env_string = ''
def get_env_vars_to_add(tools_to_activate):
env_vars_to_add = []
newpath, added_path = adjusted_path(tools_to_activate)
# Don't bother setting the path if there are no changes.
if os.environ['PATH'] != newpath:
if POWERSHELL:
env_string += '$env:PATH="' + newpath + '"\n'
elif CMD:
env_string += 'SET PATH=' + newpath + '\n'
elif CSH:
env_string += 'setenv PATH "' + newpath + '";\n'
elif BASH:
env_string += 'export PATH="' + newpath + '";\n'
else:
assert False
env_vars_to_add += [('PATH', newpath)]
if added_path:
errlog('Adding directories to PATH:')
@@ -2528,11 +2545,8 @@ def construct_env(tools_to_activate):
errlog('')
# A core variable EMSDK points to the root of Emscripten SDK directory.
env_vars = [('EMSDK', to_unix_path(emsdk_path()))]
em_config_path = os.path.normpath(dot_emscripten_path())
if to_unix_path(os.environ.get('EM_CONFIG', '')) != to_unix_path(em_config_path):
env_vars += [('EM_CONFIG', em_config_path)]
env_vars_to_add += [('EMSDK', to_unix_path(emsdk_path()))]
env_vars_to_add += [('EM_CONFIG', os.path.normpath(dot_emscripten_path()))]
for tool in tools_to_activate:
config = tool.activated_config()
@@ -2540,27 +2554,40 @@ def construct_env(tools_to_activate):
# For older emscripten versions that don't use this default we export
# EM_CACHE.
em_cache_dir = os.path.join(config['EMSCRIPTEN_ROOT'], 'cache')
env_vars += [('EM_CACHE', em_cache_dir)]
env_vars_to_add += [('EM_CACHE', em_cache_dir)]
envs = tool.activated_environment()
for env in envs:
key, value = parse_key_value(env)
value = to_native_path(tool.expand_vars(value))
env_vars += [(key, value)]
env_vars_to_add += [(key, value)]
if env_vars:
return env_vars_to_add
def construct_env(tools_to_activate):
return construct_env_with_vars(get_env_vars_to_add(tools_to_activate))
def construct_env_with_vars(env_vars_to_add):
env_string = ''
if env_vars_to_add:
errlog('Setting environment variables:')
for key, value in env_vars:
if POWERSHELL:
env_string += '$env:' + key + '="' + value + '"\n'
elif CMD:
env_string += 'SET ' + key + '=' + value + '\n'
elif CSH:
env_string += 'setenv ' + key + ' "' + value + '";\n'
elif BASH:
env_string += 'export ' + key + '="' + value + '";\n'
else:
assert False
if 'EMSDK_PYTHON' in env_vars:
for key, value in env_vars_to_add:
# Don't set env vars which are already set to the correct value.
if key not in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value):
errlog(key + ' = ' + value)
if POWERSHELL:
env_string += '$env:' + key + '="' + value + '"\n'
elif CMD:
env_string += 'SET ' + key + '=' + value + '\n'
elif CSH:
env_string += 'setenv ' + key + ' "' + value + '"\n'
elif BASH:
env_string += 'export ' + key + '="' + value + '"\n'
else:
assert False
if 'EMSDK_PYTHON' in env_vars_to_add:
# When using our bundled python we never want the user's
# PYTHONHOME or PYTHONPATH
# See https://github.com/emscripten-core/emsdk/issues/598
@@ -2579,7 +2606,6 @@ def construct_env(tools_to_activate):
else:
assert False
errlog(key + ' = ' + value)
return env_string