Source code for pymyinstall.win_installer.win_helper

# -*- coding: utf-8 -*-
"""
Helpers, inspired from `utils.py <https://github.com/winpython/winpython/blob/master/winpython/utils.py>`_


:githublink:`%|py|6`
"""
from __future__ import print_function

import os
import os.path as osp
import subprocess
import re
import sys
import locale


# =============================================================================
# Patch chebang line (courtesy of Christoph Gohlke)
# =============================================================================
[docs]def patch_shebang_line(fname, pad=b' ', fLOG=print): """ Remove absolute path to python.exe in shebang lines. :param python: python extractor :param pad: pad :param fLOG: logging function :return: boolean, True if patched, False otherwise :githublink:`%|py|27` """ if sys.version_info[0] == 2: shebang_line = re.compile(r"(#!.+pythonw?\\.exe)") # Python2.7 else: shebang_line = re.compile(b"(#!.+pythonw?\\.exe)") # Python3+ with open(fname, 'rb') as fh: content = fh.read() content = shebang_line.split(content, maxsplit=1) if len(content) != 3: return exe = os.path.basename(content[1][2:]) content[1] = b'#!' + exe + (pad * (len(content[1]) - len(exe) - 2)) content = b''.join(content) try: with open(fname, 'wb') as fh: fh.write(content) fLOG("[pymy] patched", fname) return True except Exception: fLOG("[pymy] failed to patch", fname) return False
[docs]def get_env(name, current=True): """ Return HKCU/HKLM environment variable name and value :param name: name to look for :param current: switch between *HKEY_CURRENT_USER* (True) and *HKEY_LOCAL_MACHINE* (False) :return: tuple (see below) For example, get_user_env('PATH') may returns:: ('Path', u'C:\\Program Files\\Intel\\WiFi\\bin\\') :githublink:`%|py|64` """ import winreg root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE key = winreg.OpenKey(root, "Environment") for index in range(0, winreg.QueryInfoKey(key)[1]): try: value = winreg.EnumValue(key, index) if value[0].lower() == name.lower(): # Return both value[0] and value[1] because value[0] could be # different from name (lowercase/uppercase) return value[0], value[1] except Exception: break
[docs]def set_env(name, value, current=True): """ Set HKCU/HKLM environment variables :param name: name to look for :param current: switch between *HKEY_CURRENT_USER* (True) and *HKEY_LOCAL_MACHINE* (False) :githublink:`%|py|86` """ import winreg root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE key = winreg.OpenKey(root, "Environment") try: _x, key_type = winreg.QueryValueEx(key, name) except WindowsError: key_type = winreg.REG_EXPAND_SZ key = winreg.OpenKey(root, "Environment", 0, winreg.KEY_SET_VALUE) winreg.SetValueEx(key, name, 0, key_type, value) from win32gui import SendMessageTimeout from win32con import (HWND_BROADCAST, WM_SETTINGCHANGE, SMTO_ABORTIFHUNG) SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, "Environment", SMTO_ABORTIFHUNG, 5000)
[docs]def create_shortcut(path, description, filename, arguments="", workdir="", iconpath="", iconindex=0): """ Create Windows shortcut (.lnk file) :param path: where to store the link :param description: description :param filename: link name :param arguments: arguments to store :param workdir: working directory @para iconpath icon :param iconindex: icon index :return: filename :githublink:`%|py|116` """ import pythoncom from win32com.shell import shell ilink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink) ilink.SetPath(path) ilink.SetDescription(description) if arguments: ilink.SetArguments(arguments) if workdir: ilink.SetWorkingDirectory(workdir) if iconpath or iconindex: ilink.SetIconLocation(iconpath, iconindex) # now save it. ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile) if not filename.endswith('.lnk'): filename += '.lnk' filename = os.path.join(path, filename) ipf.Save(filename, 0) return filename
[docs]def decode_fs_string(string): """ Convert string from file system charset to unicode :githublink:`%|py|140` """ charset = sys.getfilesystemencoding() if charset is None: charset = locale.getpreferredencoding() return string.decode(charset)
[docs]def exec_shell_cmd(args, path): """ Execute shell command (*args* is a list of arguments) in *path* :githublink:`%|py|148` """ # print " ".join(args) process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, shell=True) return decode_fs_string(process.stdout.read())
[docs]def get_gcc_version(path): """ Return version of the GCC compiler installed in *path* :githublink:`%|py|156` """ return exec_shell_cmd('gcc --version', path).splitlines()[0].split()[-1]
[docs]def get_r_version(path): """ Return version of the R installed in *path* :githublink:`%|py|161` """ return exec_shell_cmd('dir ..\\README.R*', path).splitlines()[-3].split("-")[-1]
[docs]def get_julia_version(path): """ Return version of the Julia installed in *path* :githublink:`%|py|166` """ return exec_shell_cmd('julia.exe -v', path).splitlines()[0].split(" ")[-1]
[docs]def python_query(cmd, path): """ Execute Python command using the Python interpreter located in *path* :githublink:`%|py|171` """ res = exec_shell_cmd('python -c "%s"' % cmd, path).splitlines() if not res: raise Exception( "CMD:\n{0}\nRES:\n{1}\nPATH:\n{2}".format(cmd, res, path)) return res[0]
[docs]def get_python_infos(path): """ Return (version, architecture) for the Python distribution located in *path*. The version number is limited to MAJOR.MINOR, the architecture is an integer: 32 or 64 :githublink:`%|py|182` """ is_64 = python_query('import sys; print(sys.maxsize > 2**32)', path) arch = {'True': 64, 'False': 32}.get(is_64, None) ver = python_query("import sys; print('%d.%d' % (sys.version_info.major, " "sys.version_info.minor))", path) if re.match(r'([0-9]*)\.([0-9]*)', ver) is None: ver = None return ver, arch
[docs]def get_python_long_version(path): """ Return long version (X.Y.Z) for the Python distribution located in *path* :githublink:`%|py|194` """ ver = python_query("import sys; print('%d.%d.%d' % " "(sys.version_info.major, sys.version_info.minor," "sys.version_info.micro))", path) if re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', ver) is None: ver = None return ver
# ============================================================================= # Patch sourcefile (instead of forking packages) # =============================================================================
[docs]def patch_sourcefile(fname, in_text, out_text, silent_mode=False): """ Replace a string in a source file :githublink:`%|py|207` """ import io if osp.isfile(fname) and not in_text == out_text: with io.open(fname, 'r') as fh: content = fh.read() new_content = content.replace(in_text, out_text) if not new_content == content: if not silent_mode: print("patching ", fname, "from", in_text, "to", out_text) with io.open(fname, 'wt') as fh: fh.write(new_content)
# ============================================================================= # Patch sourcelines (instead of forking packages) # =============================================================================
[docs]def patch_sourcelines(fname, in_line_start, out_line, endline='\n', silent_mode=False): """ Replace the middle of lines between in_line_start and endline :githublink:`%|py|225` """ import io import os.path as osp if osp.isfile(fname): with io.open(fname, 'r') as fh: contents = fh.readlines() content = "".join(contents) for l in range(len(contents)): if contents[l].startswith(in_line_start): begining, middle = in_line_start, contents[ l][len(in_line_start):] ending = "" if middle.find(endline) > 0: ending = endline + \ endline.join(middle.split(endline)[1:]) middle = middle.split(endline)[0] middle = out_line new_line = begining + middle + ending if not new_line == contents[l]: if not silent_mode: print( "patching ", fname, " from\n", contents[l], "\nto\n", new_line) contents[l] = new_line new_content = "".join(contents) if not new_content == content: # if not silent_mode: # print("patching ", fname, "from", content, "to", new_content) with io.open(fname, 'wt') as fh: try: fh.write(new_content) except Exception as e: print("impossible to patch", fname, "from", content, "to", new_content, " --- ", str(e).replace("\n", "--"))
WININST_PATTERN = (r'([a-zA-Z0-9\-\_]*|[a-zA-Z\-\_\.]*)-([0-9\.\-]*[a-z]*[0-9]?)(-Qt-([0-9\.]+))?.(win32|win\-amd64)' + r'(-py([0-9\.]+))?(-setup)?\.exe') # SOURCE_PATTERN defines what an acceptable source package name is # As of 2014-09-08 : # - the wheel package format is accepte in source directory # - the tricky regexp is tuned also to support the odd jolib naming : # . joblib-0.8.3_r1-py2.py3-none-any.whl, # . joblib-0.8.3-r1.tar.gz SOURCE_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z]*[0-9]?)(\.zip|\.tar\.gz|\-(py[2-7]*|py[2-7]*\.py[2-7]*)\-none\-any\.whl)' # WHEELBIN_PATTERN defines what an acceptable binary wheel package is # "cp([0-9]*)" to replace per cp(34) for python3.4 # "win32|win\_amd64" to replace per "win\_amd64" for 64bit WHEELBIN_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z0-9\+]*[0-9]?)-cp([0-9]*)\-none\-(win32|win\_amd64)\.whl'
[docs]def get_source_package_infos(fname): """ Return a tuple (name, version) of the Python source package :githublink:`%|py|279` """ match = re.match(SOURCE_PATTERN, osp.basename(fname)) if match is not None: return match.groups()[:2]
[docs]def do_script(this_script, python_exe=None, verbose=False, install_options=None): """ Execute a script (get-pip typically) :githublink:`%|py|287` """ if python_exe is None: python_exe = sys.executable assert osp.isfile(python_exe) myroot = os.path.dirname(python_exe) # cmd = [python_exe, myroot + r'\Scripts\pip-script.py', 'install'] cmd = [python_exe] if install_options: cmd += install_options # typically ['--no-deps'] print('script install_options', install_options) cmd += [this_script] # print('build_wheel', myroot, cmd) print("Executing ", cmd) if verbose: subprocess.call(cmd, cwd=myroot) else: p = subprocess.Popen(cmd, cwd=myroot, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate() p.stdout.close() p.stderr.close() if verbose: print("Executed " % cmd) return 'ok'
KEY_C = r"Software\Classes\%s" KEY_C0 = KEY_C % r"Python.%sFile\shell" KEY_C1 = KEY_C % r"Python.%sFile\shell\%s" KEY_C2 = KEY_C1 + r"\command" KEY_DROP0 = KEY_C % r"Python.%sFile\shellex" KEY_DROP1 = KEY_C % r"Python.%sFile\shellex\DropHandler" KEY_I = KEY_C % r"Python.%sFile\DefaultIcon" KEY_D = KEY_C % r"Python.%sFile" EWI = "Edit with IDLE" EWS = "Edit with Spyder" KEY_S = r"Software\Python" KEY_S0 = KEY_S + r"\PythonCore" KEY_S1 = KEY_S0 + r"\%s"
[docs]def register(target, current=True): """ Register a Python distribution in Windows registry :githublink:`%|py|332` """ import winreg root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE # Extensions winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".py"), "", 0, winreg.REG_SZ, "Python.File") winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyw"), "", 0, winreg.REG_SZ, "Python.NoConFile") winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyc"), "", 0, winreg.REG_SZ, "Python.CompiledFile") winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyo"), "", 0, winreg.REG_SZ, "Python.CompiledFile") # MIME types winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".py"), "Content Type", 0, winreg.REG_SZ, "text/plain") winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyw"), "Content Type", 0, winreg.REG_SZ, "text/plain") # Verbs python = osp.abspath(osp.join(target, 'python.exe')) pythonw = osp.abspath(osp.join(target, 'pythonw.exe')) spyder = osp.abspath(osp.join(target, os.pardir, 'Spyder.exe')) if not osp.isfile(spyder): spyder = '%s" "%s\\Scripts\\spyder' % (pythonw, target) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("", "open")), "", 0, winreg.REG_SZ, '"%s" "%%1" %%*' % python) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("NoCon", "open")), "", 0, winreg.REG_SZ, '"%s" "%%1" %%*' % pythonw) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("Compiled", "open")), "", 0, winreg.REG_SZ, '"%s" "%%1" %%*' % python) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("", EWI)), "", 0, winreg.REG_SZ, '"%s" "%s\\Lib\\idlelib\\idle.pyw" -n -e "%%1"' % (pythonw, target)) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("NoCon", EWI)), "", 0, winreg.REG_SZ, '"%s" "%s\\Lib\\idlelib\\idle.pyw" -n -e "%%1"' % (pythonw, target)) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("", EWS)), "", 0, winreg.REG_SZ, '"%s" "%%1"' % spyder) winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("NoCon", EWS)), "", 0, winreg.REG_SZ, '"%s" "%%1"' % spyder) # Drop support handler = "{60254CA5-953B-11CF-8C96-00AA00B8708C}" for ftype in ("", "NoCon", "Compiled"): winreg.SetValueEx(winreg.CreateKey(root, KEY_DROP1 % ftype), "", 0, winreg.REG_SZ, handler) # Icons dlls = osp.join(target, 'DLLs') winreg.SetValueEx(winreg.CreateKey(root, KEY_I % ""), "", 0, winreg.REG_SZ, r'%s\py.ico' % dlls) winreg.SetValueEx(winreg.CreateKey(root, KEY_I % "NoCon"), "", 0, winreg.REG_SZ, r'%s\py.ico' % dlls) winreg.SetValueEx(winreg.CreateKey(root, KEY_I % "Compiled"), "", 0, winreg.REG_SZ, r'%s\pyc.ico' % dlls) # Descriptions winreg.SetValueEx(winreg.CreateKey(root, KEY_D % ""), "", 0, winreg.REG_SZ, "Python File") winreg.SetValueEx(winreg.CreateKey(root, KEY_D % "NoCon"), "", 0, winreg.REG_SZ, "Python File (no console)") winreg.SetValueEx(winreg.CreateKey(root, KEY_D % "Compiled"), "", 0, winreg.REG_SZ, "Compiled Python File") # PythonCore entries ''' short_version = utils.get_python_infos(target)[0] long_version = utils.get_python_long_version(target) key_core = (KEY_S1 % short_version) + r'\\%s' winreg.SetValueEx(winreg.CreateKey(root, key_core % 'InstallPath'), "", 0, winreg.REG_SZ, target) winreg.SetValueEx(winreg.CreateKey(root, key_core % r'InstallPath\\InstallGroup'), "", 0, winreg.REG_SZ, "Python %s" % short_version) winreg.SetValueEx(winreg.CreateKey(root, key_core % 'Modules'), "", 0, winreg.REG_SZ, "") winreg.SetValueEx(winreg.CreateKey(root, key_core % 'PythonPath'), "", 0, winreg.REG_SZ, r"%s\\Lib;%s\\DLLs" % (target, target)) winreg.SetValueEx(winreg.CreateKey(root, key_core % r'Help\\Main Python Documentation'), "", 0, winreg.REG_SZ, r"%s\\Doc\\python%s.chm" % (target, long_version)) ''' # Create start menu entries for all WinPython launchers ''' for path, desc, fname in _get_shortcut_data(target, current=current): utils.create_shortcut(path, desc, fname) ''' # Register the Python ActiveX Scripting client (requires pywin32) axscript = osp.join(target, 'Lib', 'site-packages', 'win32comext', 'axscript', 'client', 'pyscript.py') if osp.isfile(axscript): subprocess.call('"%s" "%s"' % (python, axscript), cwd=target) else: print('Unable to register ActiveX: please install pywin32', file=sys.stderr)
''' def unregister(target, current=True): """ Unregister a Python distribution in Windows registry :githublink:`%|py|439` """ # Registry entries root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE short_version = utils.get_python_infos(target)[0] key_core = (KEY_S1 % short_version) + r'\\%s' for key in ( # Drop support KEY_DROP1 % "", KEY_DROP1 % "NoCon", KEY_DROP1 % "Compiled", KEY_DROP0 % "", KEY_DROP0 % "NoCon", KEY_DROP0 % "Compiled", # Icons KEY_I % "NoCon", KEY_I % "Compiled", KEY_I % "", # Edit with IDLE KEY_C2 % ("", EWI), KEY_C2 % ("NoCon", EWI), KEY_C1 % ("", EWI), KEY_C1 % ("NoCon", EWI), # Edit with Spyder KEY_C2 % ("", EWS), KEY_C2 % ("NoCon", EWS), KEY_C1 % ("", EWS), KEY_C1 % ("NoCon", EWS), # Verbs KEY_C2 % ("", "open"), KEY_C2 % ("NoCon", "open"), KEY_C2 % ("Compiled", "open"), KEY_C1 % ("", "open"), KEY_C1 % ("NoCon", "open"), KEY_C1 % ("Compiled", "open"), KEY_C0 % "", KEY_C0 % "NoCon", KEY_C0 % "Compiled", # Descriptions KEY_D % "NoCon", KEY_D % "Compiled", KEY_D % "", # PythonCore key_core % r'InstallPath\\InstallGroup', key_core % 'InstallPath', key_core % 'Modules', key_core % 'PythonPath', key_core % r'Help\\Main Python Documentation', key_core % 'Help', KEY_S1 % short_version, KEY_S0, KEY_S, ): try: print(key) winreg.DeleteKey(root, key) except WindowsError: rootkey = 'HKEY_CURRENT_USER' if current else 'HKEY_LOCAL_MACHINE' print(r'Unable to remove %s\\%s' % (rootkey, key), file=sys.stderr) # Start menu shortcuts for path, desc, fname in _get_shortcut_data(target, current=current): if osp.exists(fname): os.remove(fname) '''