You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
653 lines
24 KiB
653 lines
24 KiB
6 years ago
|
# -*- coding: utf-8 -*-
|
||
|
#
|
||
|
# Copyright © 2012 Pierre Raybaut
|
||
|
# Licensed under the terms of the MIT License
|
||
|
# (see winpython/__init__.py for details)
|
||
|
|
||
|
"""
|
||
|
WinPython utilities
|
||
|
|
||
|
Created on Tue Aug 14 14:08:40 2012
|
||
|
"""
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import os
|
||
|
import os.path as osp
|
||
|
import subprocess
|
||
|
import re
|
||
|
import tarfile
|
||
|
import zipfile
|
||
|
import tempfile
|
||
|
import shutil
|
||
|
import atexit
|
||
|
import sys
|
||
|
import stat
|
||
|
import locale
|
||
|
|
||
|
# Local imports
|
||
|
from winpython.py3compat import winreg
|
||
|
|
||
|
|
||
|
# Development only
|
||
|
TOOLS_DIR = osp.abspath(osp.join(osp.dirname(__file__), os.pardir, 't'))
|
||
|
if osp.isdir(TOOLS_DIR):
|
||
|
os.environ['PATH'] += ';%s' % TOOLS_DIR
|
||
|
ROOT_DIR = os.environ.get('WINPYTHONROOTDIR')
|
||
|
BASE_DIR = os.environ.get('WINPYTHONBASEDIR')
|
||
|
|
||
|
ROOTDIR_DOC = """
|
||
|
|
||
|
The WinPython root directory (WINPYTHONROOTDIR environment variable which
|
||
|
may be overriden with the `rootdir` option) contains the following folders:
|
||
|
* (required) `packages.win32`: contains distutils 32-bit packages
|
||
|
* (required) `packages.win-amd64`: contains distutils 64-bit packages
|
||
|
* (optional) `packages.src`: contains distutils source distributions
|
||
|
* (required) `tools`: contains architecture-independent tools
|
||
|
* (optional) `tools.win32`: contains 32-bit-specific tools
|
||
|
* (optional) `tools.win-amd64`: contains 64-bit-specific tools"""
|
||
|
|
||
|
|
||
|
def onerror(function, path, excinfo):
|
||
|
"""Error handler for `shutil.rmtree`.
|
||
|
|
||
|
If the error is due to an access error (read-only file), it
|
||
|
attempts to add write permission and then retries.
|
||
|
If the error is for another reason, it re-raises the error.
|
||
|
|
||
|
Usage: `shutil.rmtree(path, onerror=onerror)"""
|
||
|
if not os.access(path, os.W_OK):
|
||
|
# Is the error an access error?
|
||
|
os.chmod(path, stat.S_IWUSR)
|
||
|
function(path)
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
|
||
|
# Exact copy of 'spyderlib.utils.programs.is_program_installed' function
|
||
|
def is_program_installed(basename):
|
||
|
"""Return program absolute path if installed in PATH
|
||
|
Otherwise, return None"""
|
||
|
for path in os.environ["PATH"].split(os.pathsep):
|
||
|
abspath = osp.join(path, basename)
|
||
|
if osp.isfile(abspath):
|
||
|
return abspath
|
||
|
|
||
|
|
||
|
# =============================================================================
|
||
|
# Environment variables
|
||
|
# =============================================================================
|
||
|
def get_env(name, current=True):
|
||
|
"""Return HKCU/HKLM environment variable name and value
|
||
|
|
||
|
For example, get_user_env('PATH') may returns:
|
||
|
('Path', u'C:\\Program Files\\Intel\\WiFi\\bin\\')"""
|
||
|
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:
|
||
|
break
|
||
|
|
||
|
|
||
|
def set_env(name, value, current=True):
|
||
|
"""Set HKCU/HKLM environment variables"""
|
||
|
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)
|
||
|
|
||
|
|
||
|
# =============================================================================
|
||
|
# Shortcuts, start menu
|
||
|
# =============================================================================
|
||
|
|
||
|
def get_special_folder_path(path_name):
|
||
|
"""Return special folder path"""
|
||
|
from win32com.shell import shell, shellcon
|
||
|
for maybe in """
|
||
|
CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
|
||
|
CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
|
||
|
CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
|
||
|
CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
|
||
|
CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
|
||
|
if maybe == path_name:
|
||
|
csidl = getattr(shellcon, maybe)
|
||
|
return shell.SHGetSpecialFolderPath(0, csidl, False)
|
||
|
raise ValueError("%s is an unknown path ID" % (path_name,))
|
||
|
|
||
|
|
||
|
def get_winpython_start_menu_folder(current=True):
|
||
|
"""Return WinPython Start menu shortcuts folder"""
|
||
|
if current:
|
||
|
# non-admin install - always goes in this user's start menu.
|
||
|
folder = get_special_folder_path("CSIDL_PROGRAMS")
|
||
|
else:
|
||
|
try:
|
||
|
folder = get_special_folder_path("CSIDL_COMMON_PROGRAMS")
|
||
|
except OSError:
|
||
|
# No CSIDL_COMMON_PROGRAMS on this platform
|
||
|
folder = get_special_folder_path("CSIDL_PROGRAMS")
|
||
|
return osp.join(folder, 'WinPython')
|
||
|
|
||
|
|
||
|
def create_winpython_start_menu_folder(current=True):
|
||
|
"""Create WinPython Start menu folder -- remove it if it already exists"""
|
||
|
path = get_winpython_start_menu_folder(current=current)
|
||
|
if osp.isdir(path):
|
||
|
try:
|
||
|
shutil.rmtree(path, onerror=onerror)
|
||
|
except WindowsError:
|
||
|
print("Directory %s could not be removed" % path, file=sys.stderr)
|
||
|
else:
|
||
|
os.mkdir(path)
|
||
|
return path
|
||
|
|
||
|
|
||
|
def create_shortcut(path, description, filename,
|
||
|
arguments="", workdir="", iconpath="", iconindex=0):
|
||
|
"""Create Windows shortcut (.lnk file)"""
|
||
|
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'
|
||
|
ipf.Save(filename, 0)
|
||
|
|
||
|
|
||
|
# =============================================================================
|
||
|
# Misc.
|
||
|
# =============================================================================
|
||
|
|
||
|
def print_box(text):
|
||
|
"""Print text in a box"""
|
||
|
line0 = "+" + ("-"*(len(text)+2)) + "+"
|
||
|
line1 = "| " + text + " |"
|
||
|
print(("\n\n" + "\n".join([line0, line1, line0]) + "\n"))
|
||
|
|
||
|
|
||
|
def is_python_distribution(path):
|
||
|
"""Return True if path is a Python distribution"""
|
||
|
# XXX: This test could be improved but it seems to be sufficient
|
||
|
return osp.isfile(osp.join(path, 'python.exe'))\
|
||
|
and osp.isdir(osp.join(path, 'Lib', 'site-packages'))
|
||
|
|
||
|
|
||
|
# =============================================================================
|
||
|
# Shell, Python queries
|
||
|
# =============================================================================
|
||
|
|
||
|
def decode_fs_string(string):
|
||
|
"""Convert string from file system charset to unicode"""
|
||
|
charset = sys.getfilesystemencoding()
|
||
|
if charset is None:
|
||
|
charset = locale.getpreferredencoding()
|
||
|
return string.decode(charset)
|
||
|
|
||
|
|
||
|
def exec_shell_cmd(args, path):
|
||
|
"""Execute shell command (*args* is a list of arguments) in *path*"""
|
||
|
# print " ".join(args)
|
||
|
process = subprocess.Popen(args, stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.PIPE, cwd=path, shell=True)
|
||
|
return decode_fs_string(process.stdout.read())
|
||
|
|
||
|
|
||
|
def get_r_version(path):
|
||
|
"""Return version of the R installed in *path*"""
|
||
|
return exec_shell_cmd('dir ..\README.R*', path).splitlines()[-3].split("-")[-1]
|
||
|
|
||
|
|
||
|
def get_julia_version(path):
|
||
|
"""Return version of the Julia installed in *path*"""
|
||
|
return exec_shell_cmd('julia.exe -v', path).splitlines()[0].split(" ")[-1]
|
||
|
|
||
|
def get_nodejs_version(path):
|
||
|
"""Return version of the Nodejs installed in *path*"""
|
||
|
return exec_shell_cmd('node -v', path).splitlines()[0]
|
||
|
|
||
|
def get_npmjs_version(path):
|
||
|
"""Return version of the Nodejs installed in *path*"""
|
||
|
return exec_shell_cmd('npm -v', path).splitlines()[0]
|
||
|
|
||
|
def get_thg_version(path):
|
||
|
"""Return version of TortoiseHg installed in *path*"""
|
||
|
txt = exec_shell_cmd('thg version', path).splitlines()[0]
|
||
|
match = re.match('TortoiseHg Dialogs \(version ([0-9\.]*)\)', txt)
|
||
|
if match is not None:
|
||
|
return match.groups()[0]
|
||
|
|
||
|
def get_pandoc_version(path):
|
||
|
"""Return version of the Pandoc executable in *path*"""
|
||
|
return exec_shell_cmd('pandoc -v', path).splitlines()[0].split(" ")[-1]
|
||
|
|
||
|
def get_ffmpeg_version(path):
|
||
|
"""Return version of the Pandoc executable in *path*"""
|
||
|
return exec_shell_cmd('ffmpeg -version', path).splitlines()[0].split(" ")[2]
|
||
|
|
||
|
def python_query(cmd, path):
|
||
|
"""Execute Python command using the Python interpreter located in *path*"""
|
||
|
return exec_shell_cmd('python -c "%s"' % cmd, path).splitlines()[0]
|
||
|
|
||
|
|
||
|
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"""
|
||
|
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
|
||
|
|
||
|
|
||
|
def get_python_long_version(path):
|
||
|
"""Return long version (X.Y.Z) for the Python distribution located in
|
||
|
*path*"""
|
||
|
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 chebang line (courtesy of Christoph Gohlke)
|
||
|
# =============================================================================
|
||
|
def patch_shebang_line(fname, pad=b' ', to_movable=True, targetdir=""):
|
||
|
"""Remove absolute path to python.exe in shebang lines, or re-add it"""
|
||
|
|
||
|
import re
|
||
|
import sys
|
||
|
import os
|
||
|
target_dir = targetdir # movable option
|
||
|
if to_movable == False:
|
||
|
target_dir = os.path.abspath(os.path.dirname(fname))
|
||
|
target_dir = os.path.abspath(os.path.join(target_dir, r'..')) + '\\'
|
||
|
|
||
|
executable= sys.executable
|
||
|
if sys.version_info[0] == 2:
|
||
|
shebang_line = re.compile(r"(#!.*pythonw?\.exe)") # Python2.7
|
||
|
else:
|
||
|
shebang_line = re.compile(b"(#!.*pythonw?\.exe)") # Python3+
|
||
|
target_dir = target_dir.encode('utf-8')
|
||
|
with open(fname, 'rb') as fh:
|
||
|
initial_content = fh.read()
|
||
|
fh.close
|
||
|
fh = None
|
||
|
content = shebang_line.split(initial_content, maxsplit=1)
|
||
|
if len(content) != 3:
|
||
|
return
|
||
|
|
||
|
exe = os.path.basename(content[1][2:])
|
||
|
content[1] = b'#!' + target_dir + exe #+ (pad * (len(content[1]) - len(exe) - 2))
|
||
|
final_content = b''.join(content)
|
||
|
if initial_content == final_content:
|
||
|
return
|
||
|
try:
|
||
|
with open(fname, 'wb') as fo:
|
||
|
fo.write(final_content)
|
||
|
fo.close
|
||
|
fo = None
|
||
|
print("patched", fname)
|
||
|
except Exception:
|
||
|
print("failed to patch", fname)
|
||
|
|
||
|
|
||
|
# =============================================================================
|
||
|
# Patch shebang line in .py files
|
||
|
# =============================================================================
|
||
|
def patch_shebang_line_py(fname, to_movable=True, targetdir=""):
|
||
|
"""Changes shebang line in '.py' file to relative or absolue path"""
|
||
|
import fileinput
|
||
|
import re
|
||
|
import sys
|
||
|
|
||
|
if sys.version_info[0] == 2:
|
||
|
# Python 2.x doesn't create .py files for .exe files. So, Moving
|
||
|
# WinPython doesn't break running executable files.
|
||
|
return
|
||
|
if to_movable:
|
||
|
exec_path = '#!.\python.exe'
|
||
|
else:
|
||
|
exec_path = '#!' + sys.executable
|
||
|
for line in fileinput.input(fname, inplace=True):
|
||
|
if re.match('^#\!.*python\.exe$', line) is not None:
|
||
|
print(exec_path)
|
||
|
else:
|
||
|
print(line, end='')
|
||
|
|
||
|
# =============================================================================
|
||
|
# Patch sourcefile (instead of forking packages)
|
||
|
# =============================================================================
|
||
|
def patch_sourcefile(fname, in_text, out_text, silent_mode=False):
|
||
|
"""Replace a string in a source file"""
|
||
|
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)
|
||
|
# =============================================================================
|
||
|
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 """
|
||
|
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:
|
||
|
print("impossible to patch", fname, "from", content,
|
||
|
"to", new_content)
|
||
|
|
||
|
|
||
|
# =============================================================================
|
||
|
# Extract functions
|
||
|
# =============================================================================
|
||
|
def _create_temp_dir():
|
||
|
"""Create a temporary directory and remove it at exit"""
|
||
|
tmpdir = tempfile.mkdtemp(prefix='wppm_')
|
||
|
atexit.register(lambda path: shutil.rmtree(path, onerror=onerror), tmpdir)
|
||
|
return tmpdir
|
||
|
|
||
|
|
||
|
def extract_msi(fname, targetdir=None, verbose=False):
|
||
|
"""Extract .msi installer to a temporary directory (if targetdir
|
||
|
is None). Return the temporary directory path"""
|
||
|
assert fname.endswith('.msi')
|
||
|
if targetdir is None:
|
||
|
targetdir = _create_temp_dir()
|
||
|
extract = 'msiexec.exe'
|
||
|
bname = osp.basename(fname)
|
||
|
args = ['/a', '%s' % bname]
|
||
|
if not verbose:
|
||
|
args += ['/qn']
|
||
|
args += ['TARGETDIR=%s' % targetdir]
|
||
|
subprocess.call([extract]+args, cwd=osp.dirname(fname))
|
||
|
print('fname=%s' % fname)
|
||
|
print('TARGETDIR=%s' % targetdir)
|
||
|
# ensure pip if it's not 3.3
|
||
|
if '-3.3' not in targetdir:
|
||
|
subprocess.call(
|
||
|
[r'%s\%s' % (targetdir, 'python.exe'), '-m', 'ensurepip'],
|
||
|
cwd=osp.dirname(r'%s\%s' % (targetdir, 'pythons.exe')))
|
||
|
# We patch ensurepip live (shame) !!!!
|
||
|
# rational: https://github.com/pypa/pip/issues/2328
|
||
|
import glob
|
||
|
for fname in glob.glob(r'%s\Scripts\*.exe' % targetdir):
|
||
|
patch_shebang_line(fname)
|
||
|
return targetdir
|
||
|
|
||
|
|
||
|
def extract_exe(fname, targetdir=None, verbose=False):
|
||
|
"""Extract .exe archive to a temporary directory (if targetdir
|
||
|
is None). Return the temporary directory path"""
|
||
|
if targetdir is None:
|
||
|
targetdir = _create_temp_dir()
|
||
|
extract = '7z.exe'
|
||
|
assert is_program_installed(extract),\
|
||
|
"Required program '%s' was not found" % extract
|
||
|
bname = osp.basename(fname)
|
||
|
args = ['x', '-o%s' % targetdir, '-aos', bname]
|
||
|
if verbose:
|
||
|
retcode = subprocess.call([extract]+args, cwd=osp.dirname(fname))
|
||
|
else:
|
||
|
p = subprocess.Popen([extract]+args, cwd=osp.dirname(fname),
|
||
|
stdout=subprocess.PIPE)
|
||
|
p.communicate()
|
||
|
p.stdout.close()
|
||
|
retcode = p.returncode
|
||
|
if retcode != 0:
|
||
|
raise RuntimeError("Failed to extract %s (return code: %d)"
|
||
|
% (fname, retcode))
|
||
|
return targetdir
|
||
|
|
||
|
|
||
|
def extract_archive(fname, targetdir=None, verbose=False):
|
||
|
"""Extract .zip, .exe (considered to be a zip archive) or .tar.gz archive
|
||
|
to a temporary directory (if targetdir is None).
|
||
|
Return the temporary directory path"""
|
||
|
if targetdir is None:
|
||
|
targetdir = _create_temp_dir()
|
||
|
if osp.splitext(fname)[1] in ('.zip', '.exe'):
|
||
|
obj = zipfile.ZipFile(fname, mode="r")
|
||
|
elif fname.endswith('.tar.gz'):
|
||
|
obj = tarfile.open(fname, mode='r:gz')
|
||
|
else:
|
||
|
raise RuntimeError("Unsupported archive filename %s" % fname)
|
||
|
obj.extractall(path=targetdir)
|
||
|
return targetdir
|
||
|
|
||
|
|
||
|
WININST_PATTERN = r'([a-zA-Z0-9\-\_]*|[a-zA-Z\-\_\.]*)-([0-9\.\-]*[a-z]*[0-9]?)(-Qt-([0-9\.]+))?.(win32|win\-amd64)(-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]*)\-[0-9|c|o|n|e|p|m]*\-(win32|win\_amd64)\.whl'
|
||
|
|
||
|
def get_source_package_infos(fname):
|
||
|
"""Return a tuple (name, version) of the Python source package"""
|
||
|
if fname[-4:] == '.whl':
|
||
|
return osp.basename(fname).split("-")[:2]
|
||
|
match = re.match(SOURCE_PATTERN, osp.basename(fname))
|
||
|
if match is not None:
|
||
|
return match.groups()[:2]
|
||
|
|
||
|
def build_wininst(root, python_exe=None, copy_to=None,
|
||
|
architecture=None, verbose=False, installer='bdist_wininst'):
|
||
|
"""Build wininst installer from Python package located in *root*
|
||
|
and eventually copy it to *copy_to* folder.
|
||
|
Return wininst installer full path."""
|
||
|
if python_exe is None:
|
||
|
python_exe = sys.executable
|
||
|
assert osp.isfile(python_exe)
|
||
|
cmd = [python_exe, 'setup.py', 'build']
|
||
|
if architecture is not None:
|
||
|
archstr = 'win32' if architecture == 32 else 'win-amd64'
|
||
|
cmd += ['--plat-name=%s' % archstr]
|
||
|
cmd += [installer]
|
||
|
# root = a tmp dir in windows\tmp,
|
||
|
if verbose:
|
||
|
subprocess.call(cmd, cwd=root)
|
||
|
else:
|
||
|
p = subprocess.Popen(cmd, cwd=root, stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.PIPE)
|
||
|
p.communicate()
|
||
|
p.stdout.close()
|
||
|
p.stderr.close()
|
||
|
distdir = osp.join(root, 'dist')
|
||
|
if not osp.isdir(distdir):
|
||
|
raise RuntimeError("Build failed: see package README file for further"
|
||
|
" details regarding installation requirements.\n\n"
|
||
|
"For more concrete debugging infos, please try to build "
|
||
|
"the package from the command line:\n"
|
||
|
"1. Open a WinPython command prompt\n"
|
||
|
"2. Change working directory to the appropriate folder\n"
|
||
|
"3. Type `python setup.py build install`")
|
||
|
pattern = WININST_PATTERN.replace(r'(win32|win\-amd64)', archstr)
|
||
|
for distname in os.listdir(distdir):
|
||
|
match = re.match(pattern, distname)
|
||
|
if match is not None:
|
||
|
break
|
||
|
# for wheels (winpython here)
|
||
|
match = re.match(SOURCE_PATTERN, distname)
|
||
|
if match is not None:
|
||
|
break
|
||
|
match = re.match(WHEELBIN_PATTERN, distname)
|
||
|
if match is not None:
|
||
|
break
|
||
|
else:
|
||
|
raise RuntimeError("Build failed: not a pure Python package? %s" %
|
||
|
distdir)
|
||
|
src_fname = osp.join(distdir, distname)
|
||
|
if copy_to is None:
|
||
|
return src_fname
|
||
|
else:
|
||
|
dst_fname = osp.join(copy_to, distname)
|
||
|
shutil.move(src_fname, dst_fname)
|
||
|
if verbose:
|
||
|
print(("Move: %s --> %s" % (src_fname, (dst_fname))))
|
||
|
# remove tempo dir 'root' no more needed
|
||
|
shutil.rmtree(root, onerror=onerror)
|
||
|
return dst_fname
|
||
|
|
||
|
|
||
|
def direct_pip_install(fname, python_exe=None, architecture=None,
|
||
|
verbose=False, install_options=None):
|
||
|
"""Direct install via pip !"""
|
||
|
copy_to = osp.dirname(fname)
|
||
|
|
||
|
if python_exe is None:
|
||
|
python_exe = sys.executable
|
||
|
assert osp.isfile(python_exe)
|
||
|
myroot = os.path.dirname(python_exe)
|
||
|
|
||
|
cmd = [python_exe, '-m', 'pip', 'install']
|
||
|
if install_options:
|
||
|
cmd += install_options # typically ['--no-deps']
|
||
|
print('pip install_options', install_options)
|
||
|
cmd += [fname]
|
||
|
|
||
|
if verbose:
|
||
|
subprocess.call(cmd, cwd=myroot)
|
||
|
else:
|
||
|
p = subprocess.Popen(cmd, cwd=myroot, stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.PIPE)
|
||
|
stdout, stderr = p.communicate()
|
||
|
the_log = ("%s" % stdout + "\n %s" % stderr)
|
||
|
|
||
|
if ' not find ' in the_log or ' not found ' in the_log:
|
||
|
print("Failed to Install: \n %s \n" % fname)
|
||
|
print("msg: %s" % the_log)
|
||
|
raise RuntimeError
|
||
|
p.stdout.close()
|
||
|
p.stderr.close()
|
||
|
src_fname = fname
|
||
|
if copy_to is None:
|
||
|
return src_fname
|
||
|
else:
|
||
|
if verbose:
|
||
|
print("Installed %s" % src_fname)
|
||
|
return src_fname
|
||
|
|
||
|
|
||
|
def do_script(this_script, python_exe=None, copy_to=None,
|
||
|
architecture=None, verbose=False, install_options=None):
|
||
|
"""Execute a script (get-pip typically)"""
|
||
|
if python_exe is None:
|
||
|
python_exe = sys.executable
|
||
|
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)
|
||
|
if this_script:
|
||
|
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'
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
thg = get_thg_version(osp.join(BASE_DIR, 't', 'tortoisehg'))
|
||
|
print(("thg version: %r" % thg))
|
||
|
|
||
|
print_box("Test")
|
||
|
dname = sys.prefix
|
||
|
print((dname+':', '\n', get_python_infos(dname)))
|
||
|
# dname = r'E:\winpython\sandbox\python-2.7.3'
|
||
|
# print dname+':', '\n', get_python_infos(dname)
|
||
|
|
||
|
tmpdir = r'D:\Tests\winpython_tests'
|
||
|
if not osp.isdir(tmpdir):
|
||
|
os.mkdir(tmpdir)
|
||
|
print((extract_archive(osp.join(BASE_DIR, 'packages.win-amd64',
|
||
|
'winpython-0.3dev.win-amd64.exe'),
|
||
|
tmpdir)))
|
||
|
# extract_exe(osp.join(tmpdir,
|
||
|
# 'PyQwt-5.2.0-py2.6-x64-pyqt4.8.6-numpy1.6.1-1.exe'))
|
||
|
# extract_exe(osp.join(tmpdir, 'PyQt-Py2.7-x64-gpl-4.8.6-1.exe'))
|
||
|
|
||
|
# path = r'D:\Pierre\_test\xlrd-0.8.0.tar.gz'
|
||
|
# source_to_wininst(path)
|