Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Helpers, inspired from `utils.py <https://github.com/winpython/winpython/blob/master/winpython/utils.py>`_
5"""
6from __future__ import print_function
8import os
9import os.path as osp
10import subprocess
11import re
12import sys
13import locale
16# =============================================================================
17# Patch chebang line (courtesy of Christoph Gohlke)
18# =============================================================================
19def patch_shebang_line(fname, pad=b' ', fLOG=print):
20 """
21 Remove absolute path to python.exe in shebang lines.
23 @param python python extractor
24 @param pad pad
25 @param fLOG logging function
26 @return boolean, True if patched, False otherwise
27 """
28 if sys.version_info[0] == 2:
29 shebang_line = re.compile(r"(#!.+pythonw?\\.exe)") # Python2.7
30 else:
31 shebang_line = re.compile(b"(#!.+pythonw?\\.exe)") # Python3+
33 with open(fname, 'rb') as fh:
34 content = fh.read()
36 content = shebang_line.split(content, maxsplit=1)
37 if len(content) != 3:
38 return
39 exe = os.path.basename(content[1][2:])
40 content[1] = b'#!' + exe + (pad * (len(content[1]) - len(exe) - 2))
41 content = b''.join(content)
43 try:
44 with open(fname, 'wb') as fh:
45 fh.write(content)
46 fLOG("[pymy] patched", fname)
47 return True
48 except Exception:
49 fLOG("[pymy] failed to patch", fname)
50 return False
53def get_env(name, current=True):
54 """
55 Return HKCU/HKLM environment variable name and value
57 @param name name to look for
58 @param current switch between *HKEY_CURRENT_USER* (True) and *HKEY_LOCAL_MACHINE* (False)
59 @return tuple (see below)
61 For example, get_user_env('PATH') may returns::
63 ('Path', u'C:\\Program Files\\Intel\\WiFi\\bin\\')
64 """
65 import winreg
66 root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE
67 key = winreg.OpenKey(root, "Environment")
68 for index in range(0, winreg.QueryInfoKey(key)[1]):
69 try:
70 value = winreg.EnumValue(key, index)
71 if value[0].lower() == name.lower():
72 # Return both value[0] and value[1] because value[0] could be
73 # different from name (lowercase/uppercase)
74 return value[0], value[1]
75 except Exception:
76 break
79def set_env(name, value, current=True):
80 """
81 Set HKCU/HKLM environment variables
84 @param name name to look for
85 @param current switch between *HKEY_CURRENT_USER* (True) and *HKEY_LOCAL_MACHINE* (False)
86 """
87 import winreg
88 root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE
89 key = winreg.OpenKey(root, "Environment")
90 try:
91 _x, key_type = winreg.QueryValueEx(key, name)
92 except WindowsError:
93 key_type = winreg.REG_EXPAND_SZ
94 key = winreg.OpenKey(root, "Environment", 0, winreg.KEY_SET_VALUE)
95 winreg.SetValueEx(key, name, 0, key_type, value)
96 from win32gui import SendMessageTimeout
97 from win32con import (HWND_BROADCAST, WM_SETTINGCHANGE,
98 SMTO_ABORTIFHUNG)
99 SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
100 "Environment", SMTO_ABORTIFHUNG, 5000)
103def create_shortcut(path, description, filename,
104 arguments="", workdir="", iconpath="", iconindex=0):
105 """
106 Create Windows shortcut (.lnk file)
108 @param path where to store the link
109 @param description description
110 @param filename link name
111 @param arguments arguments to store
112 @param workdir working directory
113 @para iconpath icon
114 @param iconindex icon index
115 @return filename
116 """
117 import pythoncom
118 from win32com.shell import shell
119 ilink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
120 pythoncom.CLSCTX_INPROC_SERVER,
121 shell.IID_IShellLink)
122 ilink.SetPath(path)
123 ilink.SetDescription(description)
124 if arguments:
125 ilink.SetArguments(arguments)
126 if workdir:
127 ilink.SetWorkingDirectory(workdir)
128 if iconpath or iconindex:
129 ilink.SetIconLocation(iconpath, iconindex)
130 # now save it.
131 ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
132 if not filename.endswith('.lnk'):
133 filename += '.lnk'
134 filename = os.path.join(path, filename)
135 ipf.Save(filename, 0)
136 return filename
139def decode_fs_string(string):
140 """Convert string from file system charset to unicode"""
141 charset = sys.getfilesystemencoding()
142 if charset is None:
143 charset = locale.getpreferredencoding()
144 return string.decode(charset)
147def exec_shell_cmd(args, path):
148 """Execute shell command (*args* is a list of arguments) in *path*"""
149 # print " ".join(args)
150 process = subprocess.Popen(args, stdout=subprocess.PIPE,
151 stderr=subprocess.PIPE, cwd=path, shell=True)
152 return decode_fs_string(process.stdout.read())
155def get_gcc_version(path):
156 """Return version of the GCC compiler installed in *path*"""
157 return exec_shell_cmd('gcc --version', path).splitlines()[0].split()[-1]
160def get_r_version(path):
161 """Return version of the R installed in *path*"""
162 return exec_shell_cmd('dir ..\\README.R*', path).splitlines()[-3].split("-")[-1]
165def get_julia_version(path):
166 """Return version of the Julia installed in *path*"""
167 return exec_shell_cmd('julia.exe -v', path).splitlines()[0].split(" ")[-1]
170def python_query(cmd, path):
171 """Execute Python command using the Python interpreter located in *path*"""
172 res = exec_shell_cmd('python -c "%s"' % cmd, path).splitlines()
173 if not res:
174 raise Exception(
175 "CMD:\n{0}\nRES:\n{1}\nPATH:\n{2}".format(cmd, res, path))
176 return res[0]
179def get_python_infos(path):
180 """Return (version, architecture) for the Python distribution located in
181 *path*. The version number is limited to MAJOR.MINOR, the architecture is
182 an integer: 32 or 64"""
183 is_64 = python_query('import sys; print(sys.maxsize > 2**32)', path)
184 arch = {'True': 64, 'False': 32}.get(is_64, None)
185 ver = python_query("import sys; print('%d.%d' % (sys.version_info.major, "
186 "sys.version_info.minor))", path)
187 if re.match(r'([0-9]*)\.([0-9]*)', ver) is None:
188 ver = None
189 return ver, arch
192def get_python_long_version(path):
193 """Return long version (X.Y.Z) for the Python distribution located in
194 *path*"""
195 ver = python_query("import sys; print('%d.%d.%d' % "
196 "(sys.version_info.major, sys.version_info.minor,"
197 "sys.version_info.micro))", path)
198 if re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', ver) is None:
199 ver = None
200 return ver
203# =============================================================================
204# Patch sourcefile (instead of forking packages)
205# =============================================================================
206def patch_sourcefile(fname, in_text, out_text, silent_mode=False):
207 """Replace a string in a source file"""
208 import io
209 if osp.isfile(fname) and not in_text == out_text:
210 with io.open(fname, 'r') as fh:
211 content = fh.read()
212 new_content = content.replace(in_text, out_text)
213 if not new_content == content:
214 if not silent_mode:
215 print("patching ", fname, "from", in_text, "to", out_text)
216 with io.open(fname, 'wt') as fh:
217 fh.write(new_content)
219# =============================================================================
220# Patch sourcelines (instead of forking packages)
221# =============================================================================
224def patch_sourcelines(fname, in_line_start, out_line, endline='\n', silent_mode=False):
225 """Replace the middle of lines between in_line_start and endline """
226 import io
227 import os.path as osp
228 if osp.isfile(fname):
229 with io.open(fname, 'r') as fh:
230 contents = fh.readlines()
231 content = "".join(contents)
232 for l in range(len(contents)):
233 if contents[l].startswith(in_line_start):
234 begining, middle = in_line_start, contents[
235 l][len(in_line_start):]
236 ending = ""
237 if middle.find(endline) > 0:
238 ending = endline + \
239 endline.join(middle.split(endline)[1:])
240 middle = middle.split(endline)[0]
241 middle = out_line
242 new_line = begining + middle + ending
243 if not new_line == contents[l]:
244 if not silent_mode:
245 print(
246 "patching ", fname, " from\n", contents[l], "\nto\n", new_line)
247 contents[l] = new_line
248 new_content = "".join(contents)
249 if not new_content == content:
250 # if not silent_mode:
251 # print("patching ", fname, "from", content, "to", new_content)
252 with io.open(fname, 'wt') as fh:
253 try:
254 fh.write(new_content)
255 except Exception as e:
256 print("impossible to patch", fname, "from", content,
257 "to", new_content, " --- ", str(e).replace("\n", "--"))
260WININST_PATTERN = (r'([a-zA-Z0-9\-\_]*|[a-zA-Z\-\_\.]*)-([0-9\.\-]*[a-z]*[0-9]?)(-Qt-([0-9\.]+))?.(win32|win\-amd64)' +
261 r'(-py([0-9\.]+))?(-setup)?\.exe')
263# SOURCE_PATTERN defines what an acceptable source package name is
264# As of 2014-09-08 :
265# - the wheel package format is accepte in source directory
266# - the tricky regexp is tuned also to support the odd jolib naming :
267# . joblib-0.8.3_r1-py2.py3-none-any.whl,
268# . joblib-0.8.3-r1.tar.gz
270SOURCE_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)'
272# WHEELBIN_PATTERN defines what an acceptable binary wheel package is
273# "cp([0-9]*)" to replace per cp(34) for python3.4
274# "win32|win\_amd64" to replace per "win\_amd64" for 64bit
275WHEELBIN_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z0-9\+]*[0-9]?)-cp([0-9]*)\-none\-(win32|win\_amd64)\.whl'
278def get_source_package_infos(fname):
279 """Return a tuple (name, version) of the Python source package"""
280 match = re.match(SOURCE_PATTERN, osp.basename(fname))
281 if match is not None:
282 return match.groups()[:2]
285def do_script(this_script, python_exe=None,
286 verbose=False, install_options=None):
287 """Execute a script (get-pip typically)"""
288 if python_exe is None:
289 python_exe = sys.executable
290 assert osp.isfile(python_exe)
291 myroot = os.path.dirname(python_exe)
293 # cmd = [python_exe, myroot + r'\Scripts\pip-script.py', 'install']
294 cmd = [python_exe]
295 if install_options:
296 cmd += install_options # typically ['--no-deps']
297 print('script install_options', install_options)
298 cmd += [this_script]
299 # print('build_wheel', myroot, cmd)
300 print("Executing ", cmd)
302 if verbose:
303 subprocess.call(cmd, cwd=myroot)
304 else:
305 p = subprocess.Popen(cmd, cwd=myroot, stdout=subprocess.PIPE,
306 stderr=subprocess.PIPE)
307 p.communicate()
308 p.stdout.close()
309 p.stderr.close()
310 if verbose:
311 print("Executed " % cmd)
312 return 'ok'
315KEY_C = r"Software\Classes\%s"
316KEY_C0 = KEY_C % r"Python.%sFile\shell"
317KEY_C1 = KEY_C % r"Python.%sFile\shell\%s"
318KEY_C2 = KEY_C1 + r"\command"
319KEY_DROP0 = KEY_C % r"Python.%sFile\shellex"
320KEY_DROP1 = KEY_C % r"Python.%sFile\shellex\DropHandler"
321KEY_I = KEY_C % r"Python.%sFile\DefaultIcon"
322KEY_D = KEY_C % r"Python.%sFile"
323EWI = "Edit with IDLE"
324EWS = "Edit with Spyder"
326KEY_S = r"Software\Python"
327KEY_S0 = KEY_S + r"\PythonCore"
328KEY_S1 = KEY_S0 + r"\%s"
331def register(target, current=True):
332 """Register a Python distribution in Windows registry"""
333 import winreg
334 root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE
336 # Extensions
337 winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".py"),
338 "", 0, winreg.REG_SZ, "Python.File")
339 winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyw"),
340 "", 0, winreg.REG_SZ, "Python.NoConFile")
341 winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyc"),
342 "", 0, winreg.REG_SZ, "Python.CompiledFile")
343 winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyo"),
344 "", 0, winreg.REG_SZ, "Python.CompiledFile")
346 # MIME types
347 winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".py"),
348 "Content Type", 0, winreg.REG_SZ, "text/plain")
349 winreg.SetValueEx(winreg.CreateKey(root, KEY_C % ".pyw"),
350 "Content Type", 0, winreg.REG_SZ, "text/plain")
352 # Verbs
353 python = osp.abspath(osp.join(target, 'python.exe'))
354 pythonw = osp.abspath(osp.join(target, 'pythonw.exe'))
355 spyder = osp.abspath(osp.join(target, os.pardir, 'Spyder.exe'))
356 if not osp.isfile(spyder):
357 spyder = '%s" "%s\\Scripts\\spyder' % (pythonw, target)
358 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("", "open")),
359 "", 0, winreg.REG_SZ, '"%s" "%%1" %%*' % python)
360 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("NoCon", "open")),
361 "", 0, winreg.REG_SZ, '"%s" "%%1" %%*' % pythonw)
362 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("Compiled", "open")),
363 "", 0, winreg.REG_SZ, '"%s" "%%1" %%*' % python)
364 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("", EWI)),
365 "", 0, winreg.REG_SZ,
366 '"%s" "%s\\Lib\\idlelib\\idle.pyw" -n -e "%%1"'
367 % (pythonw, target))
368 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("NoCon", EWI)),
369 "", 0, winreg.REG_SZ,
370 '"%s" "%s\\Lib\\idlelib\\idle.pyw" -n -e "%%1"'
371 % (pythonw, target))
372 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("", EWS)),
373 "", 0, winreg.REG_SZ, '"%s" "%%1"' % spyder)
374 winreg.SetValueEx(winreg.CreateKey(root, KEY_C2 % ("NoCon", EWS)),
375 "", 0, winreg.REG_SZ, '"%s" "%%1"' % spyder)
377 # Drop support
378 handler = "{60254CA5-953B-11CF-8C96-00AA00B8708C}"
379 for ftype in ("", "NoCon", "Compiled"):
380 winreg.SetValueEx(winreg.CreateKey(root, KEY_DROP1 % ftype),
381 "", 0, winreg.REG_SZ, handler)
383 # Icons
384 dlls = osp.join(target, 'DLLs')
385 winreg.SetValueEx(winreg.CreateKey(root, KEY_I % ""),
386 "", 0, winreg.REG_SZ, r'%s\py.ico' % dlls)
387 winreg.SetValueEx(winreg.CreateKey(root, KEY_I % "NoCon"),
388 "", 0, winreg.REG_SZ, r'%s\py.ico' % dlls)
389 winreg.SetValueEx(winreg.CreateKey(root, KEY_I % "Compiled"),
390 "", 0, winreg.REG_SZ, r'%s\pyc.ico' % dlls)
392 # Descriptions
393 winreg.SetValueEx(winreg.CreateKey(root, KEY_D % ""),
394 "", 0, winreg.REG_SZ, "Python File")
395 winreg.SetValueEx(winreg.CreateKey(root, KEY_D % "NoCon"),
396 "", 0, winreg.REG_SZ, "Python File (no console)")
397 winreg.SetValueEx(winreg.CreateKey(root, KEY_D % "Compiled"),
398 "", 0, winreg.REG_SZ, "Compiled Python File")
400 # PythonCore entries
401 '''
402 short_version = utils.get_python_infos(target)[0]
403 long_version = utils.get_python_long_version(target)
404 key_core = (KEY_S1 % short_version) + r'\\%s'
405 winreg.SetValueEx(winreg.CreateKey(root, key_core % 'InstallPath'),
406 "", 0, winreg.REG_SZ, target)
407 winreg.SetValueEx(winreg.CreateKey(root,
408 key_core % r'InstallPath\\InstallGroup'),
409 "", 0, winreg.REG_SZ, "Python %s" % short_version)
410 winreg.SetValueEx(winreg.CreateKey(root, key_core % 'Modules'),
411 "", 0, winreg.REG_SZ, "")
412 winreg.SetValueEx(winreg.CreateKey(root, key_core % 'PythonPath'),
413 "", 0, winreg.REG_SZ,
414 r"%s\\Lib;%s\\DLLs" % (target, target))
415 winreg.SetValueEx(winreg.CreateKey(root,
416 key_core % r'Help\\Main Python Documentation'),
417 "", 0, winreg.REG_SZ,
418 r"%s\\Doc\\python%s.chm" % (target, long_version))
419 '''
421 # Create start menu entries for all WinPython launchers
422 '''
423 for path, desc, fname in _get_shortcut_data(target, current=current):
424 utils.create_shortcut(path, desc, fname)
425 '''
427 # Register the Python ActiveX Scripting client (requires pywin32)
428 axscript = osp.join(target, 'Lib', 'site-packages', 'win32comext',
429 'axscript', 'client', 'pyscript.py')
430 if osp.isfile(axscript):
431 subprocess.call('"%s" "%s"' % (python, axscript), cwd=target)
432 else:
433 print('Unable to register ActiveX: please install pywin32',
434 file=sys.stderr)
437'''
438def unregister(target, current=True):
439 """Unregister a Python distribution in Windows registry"""
440 # Registry entries
441 root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE
442 short_version = utils.get_python_infos(target)[0]
443 key_core = (KEY_S1 % short_version) + r'\\%s'
444 for key in (
445 # Drop support
446 KEY_DROP1 % "", KEY_DROP1 % "NoCon", KEY_DROP1 % "Compiled",
447 KEY_DROP0 % "", KEY_DROP0 % "NoCon", KEY_DROP0 % "Compiled",
448 # Icons
449 KEY_I % "NoCon", KEY_I % "Compiled", KEY_I % "",
450 # Edit with IDLE
451 KEY_C2 % ("", EWI), KEY_C2 % ("NoCon", EWI),
452 KEY_C1 % ("", EWI), KEY_C1 % ("NoCon", EWI),
453 # Edit with Spyder
454 KEY_C2 % ("", EWS), KEY_C2 % ("NoCon", EWS),
455 KEY_C1 % ("", EWS), KEY_C1 % ("NoCon", EWS),
456 # Verbs
457 KEY_C2 % ("", "open"),
458 KEY_C2 % ("NoCon", "open"),
459 KEY_C2 % ("Compiled", "open"),
460 KEY_C1 % ("", "open"),
461 KEY_C1 % ("NoCon", "open"),
462 KEY_C1 % ("Compiled", "open"),
463 KEY_C0 % "", KEY_C0 % "NoCon", KEY_C0 % "Compiled",
464 # Descriptions
465 KEY_D % "NoCon", KEY_D % "Compiled", KEY_D % "",
466 # PythonCore
467 key_core % r'InstallPath\\InstallGroup',
468 key_core % 'InstallPath',
469 key_core % 'Modules',
470 key_core % 'PythonPath',
471 key_core % r'Help\\Main Python Documentation',
472 key_core % 'Help',
473 KEY_S1 % short_version, KEY_S0, KEY_S,
474 ):
475 try:
476 print(key)
477 winreg.DeleteKey(root, key)
478 except WindowsError:
479 rootkey = 'HKEY_CURRENT_USER' if current else 'HKEY_LOCAL_MACHINE'
480 print(r'Unable to remove %s\\%s' % (rootkey, key), file=sys.stderr)
482 # Start menu shortcuts
483 for path, desc, fname in _get_shortcut_data(target, current=current):
484 if osp.exists(fname):
485 os.remove(fname)
486'''