utils.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright © 2012 Pierre Raybaut
  4. # Licensed under the terms of the MIT License
  5. # (see winpython/__init__.py for details)
  6. """
  7. WinPython utilities
  8. Created on Tue Aug 14 14:08:40 2012
  9. """
  10. from __future__ import print_function
  11. import os
  12. import os.path as osp
  13. import subprocess
  14. import re
  15. import tarfile
  16. import zipfile
  17. import tempfile
  18. import shutil
  19. import atexit
  20. import sys
  21. import stat
  22. import locale
  23. # Local imports
  24. from winpython.py3compat import winreg
  25. def onerror(function, path, excinfo):
  26. """Error handler for `shutil.rmtree`.
  27. If the error is due to an access error (read-only file), it
  28. attempts to add write permission and then retries.
  29. If the error is for another reason, it re-raises the error.
  30. Usage: `shutil.rmtree(path, onerror=onerror)"""
  31. if not os.access(path, os.W_OK):
  32. # Is the error an access error?
  33. os.chmod(path, stat.S_IWUSR)
  34. function(path)
  35. else:
  36. raise
  37. # Exact copy of 'spyderlib.utils.programs.is_program_installed' function
  38. def is_program_installed(basename):
  39. """Return program absolute path if installed in PATH
  40. Otherwise, return None"""
  41. for path in os.environ["PATH"].split(os.pathsep):
  42. abspath = osp.join(path, basename)
  43. if osp.isfile(abspath):
  44. return abspath
  45. # =============================================================================
  46. # Environment variables
  47. # =============================================================================
  48. def get_env(name, current=True):
  49. """Return HKCU/HKLM environment variable name and value
  50. For example, get_user_env('PATH') may returns:
  51. ('Path', u'C:\\Program Files\\Intel\\WiFi\\bin\\')"""
  52. root = (
  53. winreg.HKEY_CURRENT_USER
  54. if current
  55. else winreg.HKEY_LOCAL_MACHINE
  56. )
  57. key = winreg.OpenKey(root, "Environment")
  58. for index in range(0, winreg.QueryInfoKey(key)[1]):
  59. try:
  60. value = winreg.EnumValue(key, index)
  61. if value[0].lower() == name.lower():
  62. # Return both value[0] and value[1] because value[0] could be
  63. # different from name (lowercase/uppercase)
  64. return value[0], value[1]
  65. except:
  66. break
  67. def set_env(name, value, current=True):
  68. """Set HKCU/HKLM environment variables"""
  69. root = (
  70. winreg.HKEY_CURRENT_USER
  71. if current
  72. else winreg.HKEY_LOCAL_MACHINE
  73. )
  74. key = winreg.OpenKey(root, "Environment")
  75. try:
  76. _x, key_type = winreg.QueryValueEx(key, name)
  77. except WindowsError:
  78. key_type = winreg.REG_EXPAND_SZ
  79. key = winreg.OpenKey(
  80. root, "Environment", 0, winreg.KEY_SET_VALUE
  81. )
  82. winreg.SetValueEx(key, name, 0, key_type, value)
  83. from win32gui import SendMessageTimeout
  84. from win32con import (
  85. HWND_BROADCAST,
  86. WM_SETTINGCHANGE,
  87. SMTO_ABORTIFHUNG,
  88. )
  89. SendMessageTimeout(
  90. HWND_BROADCAST,
  91. WM_SETTINGCHANGE,
  92. 0,
  93. "Environment",
  94. SMTO_ABORTIFHUNG,
  95. 5000,
  96. )
  97. #==============================================================================
  98. # https://stackoverflow.com/questions/580924/how-to-access-a-files-properties-on-windows
  99. def getFileProperties(fname):
  100. #==============================================================================
  101. """
  102. Read all properties of the given file return them as a dictionary.
  103. """
  104. import win32api
  105. propNames = ('Comments', 'InternalName', 'ProductName',
  106. 'CompanyName', 'LegalCopyright', 'ProductVersion',
  107. 'FileDescription', 'LegalTrademarks', 'PrivateBuild',
  108. 'FileVersion', 'OriginalFilename', 'SpecialBuild')
  109. props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None}
  110. try:
  111. # backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc
  112. fixedInfo = win32api.GetFileVersionInfo(fname, '\\')
  113. props['FixedFileInfo'] = fixedInfo
  114. props['FileVersion'] = "%d.%d.%d.%d" % (fixedInfo['FileVersionMS'] / 65536,
  115. fixedInfo['FileVersionMS'] % 65536, fixedInfo['FileVersionLS'] / 65536,
  116. fixedInfo['FileVersionLS'] % 65536)
  117. # \VarFileInfo\Translation returns list of available (language, codepage)
  118. # pairs that can be used to retreive string info. We are using only the first pair.
  119. lang, codepage = win32api.GetFileVersionInfo(fname, '\\VarFileInfo\\Translation')[0]
  120. # any other must be of the form \StringfileInfo\%04X%04X\parm_name, middle
  121. # two are language/codepage pair returned from above
  122. strInfo = {}
  123. for propName in propNames:
  124. strInfoPath = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, propName)
  125. ## print str_info
  126. strInfo[propName] = win32api.GetFileVersionInfo(fname, strInfoPath)
  127. props['StringFileInfo'] = strInfo
  128. except:
  129. pass
  130. return props
  131. # =============================================================================
  132. # Shortcuts, start menu
  133. # =============================================================================
  134. def get_special_folder_path(path_name):
  135. """Return special folder path"""
  136. from win32com.shell import shell, shellcon
  137. for maybe in """
  138. CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
  139. CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
  140. CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
  141. CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
  142. CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
  143. if maybe == path_name:
  144. csidl = getattr(shellcon, maybe)
  145. return shell.SHGetSpecialFolderPath(
  146. 0, csidl, False
  147. )
  148. raise ValueError(
  149. "%s is an unknown path ID" % (path_name,)
  150. )
  151. def get_winpython_start_menu_folder(current=True):
  152. """Return WinPython Start menu shortcuts folder"""
  153. if current:
  154. # non-admin install - always goes in this user's start menu.
  155. folder = get_special_folder_path("CSIDL_PROGRAMS")
  156. else:
  157. try:
  158. folder = get_special_folder_path(
  159. "CSIDL_COMMON_PROGRAMS"
  160. )
  161. except OSError:
  162. # No CSIDL_COMMON_PROGRAMS on this platform
  163. folder = get_special_folder_path(
  164. "CSIDL_PROGRAMS"
  165. )
  166. return osp.join(folder, 'WinPython')
  167. def create_winpython_start_menu_folder(current=True):
  168. """Create WinPython Start menu folder -- remove it if it already exists"""
  169. path = get_winpython_start_menu_folder(current=current)
  170. if osp.isdir(path):
  171. try:
  172. shutil.rmtree(path, onerror=onerror)
  173. except WindowsError:
  174. print(
  175. "Directory %s could not be removed" % path,
  176. file=sys.stderr,
  177. )
  178. else:
  179. os.mkdir(path)
  180. return path
  181. def create_shortcut(
  182. path,
  183. description,
  184. filename,
  185. arguments="",
  186. workdir="",
  187. iconpath="",
  188. iconindex=0,
  189. ):
  190. """Create Windows shortcut (.lnk file)"""
  191. import pythoncom
  192. from win32com.shell import shell
  193. ilink = pythoncom.CoCreateInstance(
  194. shell.CLSID_ShellLink,
  195. None,
  196. pythoncom.CLSCTX_INPROC_SERVER,
  197. shell.IID_IShellLink,
  198. )
  199. ilink.SetPath(path)
  200. ilink.SetDescription(description)
  201. if arguments:
  202. ilink.SetArguments(arguments)
  203. if workdir:
  204. ilink.SetWorkingDirectory(workdir)
  205. if iconpath or iconindex:
  206. ilink.SetIconLocation(iconpath, iconindex)
  207. # now save it.
  208. ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
  209. if not filename.endswith('.lnk'):
  210. filename += '.lnk'
  211. ipf.Save(filename, 0)
  212. # =============================================================================
  213. # Misc.
  214. # =============================================================================
  215. def print_box(text):
  216. """Print text in a box"""
  217. line0 = "+" + ("-" * (len(text) + 2)) + "+"
  218. line1 = "| " + text + " |"
  219. print(
  220. ("\n\n" + "\n".join([line0, line1, line0]) + "\n")
  221. )
  222. def is_python_distribution(path):
  223. """Return True if path is a Python distribution"""
  224. # XXX: This test could be improved but it seems to be sufficient
  225. return osp.isfile(
  226. osp.join(path, 'python.exe')
  227. ) and osp.isdir(osp.join(path, 'Lib', 'site-packages'))
  228. # =============================================================================
  229. # Shell, Python queries
  230. # =============================================================================
  231. def decode_fs_string(string):
  232. """Convert string from file system charset to unicode"""
  233. charset = sys.getfilesystemencoding()
  234. if charset is None:
  235. charset = locale.getpreferredencoding()
  236. return string.decode(charset)
  237. def exec_shell_cmd(args, path):
  238. """Execute shell command (*args* is a list of arguments) in *path*"""
  239. # print " ".join(args)
  240. process = subprocess.Popen(
  241. args,
  242. stdout=subprocess.PIPE,
  243. stderr=subprocess.PIPE,
  244. cwd=path,
  245. shell=True,
  246. )
  247. return decode_fs_string(process.stdout.read())
  248. def exec_run_cmd(args, path=None):
  249. """run a single command (*args* is a list of arguments) in optional *path*"""
  250. # only applicable to Python-3.5+
  251. # python-3.7+ allows to replace "stdout and stderr ", per "capture_output=True"
  252. if path:
  253. process = subprocess.run(args,
  254. stdout=subprocess.PIPE,
  255. stderr=subprocess.PIPE,
  256. cwd=path)
  257. else:
  258. process = subprocess.run(args,
  259. stdout=subprocess.PIPE,
  260. stderr=subprocess.PIPE)
  261. return decode_fs_string(process.stdout)
  262. def get_r_version(path):
  263. """Return version of the R installed in *path*"""
  264. return (
  265. exec_shell_cmd('dir ..\README.R*', path)
  266. .splitlines()[-3]
  267. .split("-")[-1]
  268. )
  269. def get_julia_version(path):
  270. """Return version of the Julia installed in *path*"""
  271. return (
  272. exec_shell_cmd('julia.exe -v', path)
  273. .splitlines()[0]
  274. .split(" ")[-1]
  275. )
  276. def get_nodejs_version(path):
  277. """Return version of the Nodejs installed in *path*"""
  278. return exec_shell_cmd('node -v', path).splitlines()[0]
  279. def get_npmjs_version(path):
  280. """Return version of the Nodejs installed in *path*"""
  281. return exec_shell_cmd('npm -v', path).splitlines()[0]
  282. def get_pandoc_version(path):
  283. """Return version of the Pandoc executable in *path*"""
  284. return (
  285. exec_shell_cmd('pandoc -v', path)
  286. .splitlines()[0]
  287. .split(" ")[-1]
  288. )
  289. def python_query(cmd, path):
  290. """Execute Python command using the Python interpreter located in *path*"""
  291. return exec_shell_cmd(
  292. 'python -c "%s"' % cmd, path
  293. ).splitlines()[0]
  294. def get_python_infos(path):
  295. """Return (version, architecture) for the Python distribution located in
  296. *path*. The version number is limited to MAJOR.MINOR, the architecture is
  297. an integer: 32 or 64"""
  298. is_64 = python_query(
  299. 'import sys; print(sys.maxsize > 2**32)', path
  300. )
  301. arch = {'True': 64, 'False': 32}.get(is_64, None)
  302. ver = python_query(
  303. "import sys; print('%d.%d' % (sys.version_info.major, "
  304. "sys.version_info.minor))",
  305. path,
  306. )
  307. if re.match(r'([0-9]*)\.([0-9]*)', ver) is None:
  308. ver = None
  309. return ver, arch
  310. def get_python_long_version(path):
  311. """Return long version (X.Y.Z) for the Python distribution located in
  312. *path*"""
  313. ver = python_query(
  314. "import sys; print('%d.%d.%d' % "
  315. "(sys.version_info.major, sys.version_info.minor,"
  316. "sys.version_info.micro))",
  317. path,
  318. )
  319. if (
  320. re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', ver)
  321. is None
  322. ):
  323. ver = None
  324. return ver
  325. # =============================================================================
  326. # Patch chebang line (courtesy of Christoph Gohlke)
  327. # =============================================================================
  328. def patch_shebang_line(
  329. fname, pad=b' ', to_movable=True, targetdir=""
  330. ):
  331. """Remove absolute path to python.exe in shebang lines, or re-add it"""
  332. import re
  333. import sys
  334. import os
  335. target_dir = targetdir # movable option
  336. if to_movable == False:
  337. target_dir = os.path.abspath(os.path.dirname(fname))
  338. target_dir = (
  339. os.path.abspath(os.path.join(target_dir, r'..'))
  340. + '\\'
  341. )
  342. executable = sys.executable
  343. if sys.version_info[0] == 2:
  344. shebang_line = re.compile(
  345. r"(#!.*pythonw?\.exe)"
  346. ) # Python2.7
  347. else:
  348. shebang_line = re.compile(
  349. b"(#!.*pythonw?\.exe)"
  350. ) # Python3+
  351. target_dir = target_dir.encode('utf-8')
  352. with open(fname, 'rb') as fh:
  353. initial_content = fh.read()
  354. fh.close
  355. fh = None
  356. content = shebang_line.split(
  357. initial_content, maxsplit=1
  358. )
  359. if len(content) != 3:
  360. return
  361. exe = os.path.basename(content[1][2:])
  362. content[1] = (
  363. b'#!' + target_dir + exe
  364. ) # + (pad * (len(content[1]) - len(exe) - 2))
  365. final_content = b''.join(content)
  366. if initial_content == final_content:
  367. return
  368. try:
  369. with open(fname, 'wb') as fo:
  370. fo.write(final_content)
  371. fo.close
  372. fo = None
  373. print("patched", fname)
  374. except Exception:
  375. print("failed to patch", fname)
  376. # =============================================================================
  377. # Patch shebang line in .py files
  378. # =============================================================================
  379. def patch_shebang_line_py(
  380. fname, to_movable=True, targetdir=""
  381. ):
  382. """Changes shebang line in '.py' file to relative or absolue path"""
  383. import fileinput
  384. import re
  385. import sys
  386. if sys.version_info[0] == 2:
  387. # Python 2.x doesn't create .py files for .exe files. So, Moving
  388. # WinPython doesn't break running executable files.
  389. return
  390. if to_movable:
  391. exec_path = '#!.\python.exe'
  392. else:
  393. exec_path = '#!' + sys.executable
  394. for line in fileinput.input(fname, inplace=True):
  395. if re.match('^#\!.*python\.exe$', line) is not None:
  396. print(exec_path)
  397. else:
  398. print(line, end='')
  399. # =============================================================================
  400. # Patch sourcefile (instead of forking packages)
  401. # =============================================================================
  402. def patch_sourcefile(
  403. fname, in_text, out_text, silent_mode=False
  404. ):
  405. """Replace a string in a source file"""
  406. import io
  407. if osp.isfile(fname) and not in_text == out_text:
  408. with io.open(fname, 'r') as fh:
  409. content = fh.read()
  410. new_content = content.replace(in_text, out_text)
  411. if not new_content == content:
  412. if not silent_mode:
  413. print(
  414. "patching ",
  415. fname,
  416. "from",
  417. in_text,
  418. "to",
  419. out_text,
  420. )
  421. with io.open(fname, 'wt') as fh:
  422. fh.write(new_content)
  423. # =============================================================================
  424. # Patch sourcelines (instead of forking packages)
  425. # =============================================================================
  426. def patch_sourcelines(
  427. fname,
  428. in_line_start,
  429. out_line,
  430. endline='\n',
  431. silent_mode=False,
  432. ):
  433. """Replace the middle of lines between in_line_start and endline """
  434. import io
  435. import os.path as osp
  436. if osp.isfile(fname):
  437. with io.open(fname, 'r') as fh:
  438. contents = fh.readlines()
  439. content = "".join(contents)
  440. for l in range(len(contents)):
  441. if contents[l].startswith(in_line_start):
  442. begining, middle = (
  443. in_line_start,
  444. contents[l][len(in_line_start) :],
  445. )
  446. ending = ""
  447. if middle.find(endline) > 0:
  448. ending = endline + endline.join(
  449. middle.split(endline)[1:]
  450. )
  451. middle = middle.split(endline)[0]
  452. middle = out_line
  453. new_line = begining + middle + ending
  454. if not new_line == contents[l]:
  455. if not silent_mode:
  456. print(
  457. "patching ",
  458. fname,
  459. " from\n",
  460. contents[l],
  461. "\nto\n",
  462. new_line,
  463. )
  464. contents[l] = new_line
  465. new_content = "".join(contents)
  466. if not new_content == content:
  467. # if not silent_mode:
  468. # print("patching ", fname, "from", content, "to", new_content)
  469. with io.open(fname, 'wt') as fh:
  470. try:
  471. fh.write(new_content)
  472. except:
  473. print(
  474. "impossible to patch",
  475. fname,
  476. "from",
  477. content,
  478. "to",
  479. new_content,
  480. )
  481. # =============================================================================
  482. # Extract functions
  483. # =============================================================================
  484. def _create_temp_dir():
  485. """Create a temporary directory and remove it at exit"""
  486. tmpdir = tempfile.mkdtemp(prefix='wppm_')
  487. atexit.register(
  488. lambda path: shutil.rmtree(path, onerror=onerror),
  489. tmpdir,
  490. )
  491. return tmpdir
  492. def extract_exe(fname, targetdir=None, verbose=False):
  493. """Extract .exe archive to a temporary directory (if targetdir
  494. is None). Return the temporary directory path"""
  495. if targetdir is None:
  496. targetdir = _create_temp_dir()
  497. extract = '7z.exe'
  498. assert is_program_installed(extract), (
  499. "Required program '%s' was not found" % extract
  500. )
  501. bname = osp.basename(fname)
  502. args = ['x', '-o%s' % targetdir, '-aos', bname]
  503. if verbose:
  504. retcode = subprocess.call(
  505. [extract] + args, cwd=osp.dirname(fname)
  506. )
  507. else:
  508. p = subprocess.Popen(
  509. [extract] + args,
  510. cwd=osp.dirname(fname),
  511. stdout=subprocess.PIPE,
  512. )
  513. p.communicate()
  514. p.stdout.close()
  515. retcode = p.returncode
  516. if retcode != 0:
  517. raise RuntimeError(
  518. "Failed to extract %s (return code: %d)"
  519. % (fname, retcode)
  520. )
  521. return targetdir
  522. def extract_archive(fname, targetdir=None, verbose=False):
  523. """Extract .zip, .exe (considered to be a zip archive) or .tar.gz archive
  524. to a temporary directory (if targetdir is None).
  525. Return the temporary directory path"""
  526. if targetdir is None:
  527. targetdir = _create_temp_dir()
  528. else:
  529. try:
  530. os.mkdir(targetdir)
  531. except:
  532. pass
  533. if osp.splitext(fname)[1] in ('.zip', '.exe'):
  534. obj = zipfile.ZipFile(fname, mode="r")
  535. elif fname.endswith('.tar.gz'):
  536. obj = tarfile.open(fname, mode='r:gz')
  537. else:
  538. raise RuntimeError(
  539. "Unsupported archive filename %s" % fname
  540. )
  541. obj.extractall(path=targetdir)
  542. return targetdir
  543. 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'
  544. # SOURCE_PATTERN defines what an acceptable source package name is
  545. # As of 2014-09-08 :
  546. # - the wheel package format is accepte in source directory
  547. # - the tricky regexp is tuned also to support the odd jolib naming :
  548. # . joblib-0.8.3_r1-py2.py3-none-any.whl,
  549. # . joblib-0.8.3-r1.tar.gz
  550. 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)'
  551. # WHEELBIN_PATTERN defines what an acceptable binary wheel package is
  552. # "cp([0-9]*)" to replace per cp(34) for python3.4
  553. # "win32|win\_amd64" to replace per "win\_amd64" for 64bit
  554. 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'
  555. def get_source_package_infos(fname):
  556. """Return a tuple (name, version) of the Python source package"""
  557. if fname[-4:] == '.whl':
  558. return osp.basename(fname).split("-")[:2]
  559. match = re.match(SOURCE_PATTERN, osp.basename(fname))
  560. if match is not None:
  561. return match.groups()[:2]
  562. def build_wininst(
  563. root,
  564. python_exe=None,
  565. copy_to=None,
  566. architecture=None,
  567. verbose=False,
  568. installer='bdist_wininst',
  569. ):
  570. """Build wininst installer from Python package located in *root*
  571. and eventually copy it to *copy_to* folder.
  572. Return wininst installer full path."""
  573. if python_exe is None:
  574. python_exe = sys.executable
  575. assert osp.isfile(python_exe)
  576. cmd = [python_exe, 'setup.py', 'build']
  577. if architecture is not None:
  578. archstr = (
  579. 'win32' if architecture == 32 else 'win-amd64'
  580. )
  581. cmd += ['--plat-name=%s' % archstr]
  582. cmd += [installer]
  583. # root = a tmp dir in windows\tmp,
  584. if verbose:
  585. subprocess.call(cmd, cwd=root)
  586. else:
  587. p = subprocess.Popen(
  588. cmd,
  589. cwd=root,
  590. stdout=subprocess.PIPE,
  591. stderr=subprocess.PIPE,
  592. )
  593. p.communicate()
  594. p.stdout.close()
  595. p.stderr.close()
  596. distdir = osp.join(root, 'dist')
  597. if not osp.isdir(distdir):
  598. raise RuntimeError(
  599. "Build failed: see package README file for further"
  600. " details regarding installation requirements.\n\n"
  601. "For more concrete debugging infos, please try to build "
  602. "the package from the command line:\n"
  603. "1. Open a WinPython command prompt\n"
  604. "2. Change working directory to the appropriate folder\n"
  605. "3. Type `python setup.py build install`"
  606. )
  607. pattern = WININST_PATTERN.replace(
  608. r'(win32|win\-amd64)', archstr
  609. )
  610. for distname in os.listdir(distdir):
  611. match = re.match(pattern, distname)
  612. if match is not None:
  613. break
  614. # for wheels (winpython here)
  615. match = re.match(SOURCE_PATTERN, distname)
  616. if match is not None:
  617. break
  618. match = re.match(WHEELBIN_PATTERN, distname)
  619. if match is not None:
  620. break
  621. else:
  622. raise RuntimeError(
  623. "Build failed: not a pure Python package? %s"
  624. % distdir
  625. )
  626. src_fname = osp.join(distdir, distname)
  627. if copy_to is None:
  628. return src_fname
  629. else:
  630. dst_fname = osp.join(copy_to, distname)
  631. shutil.move(src_fname, dst_fname)
  632. if verbose:
  633. print(
  634. (
  635. "Move: %s --> %s"
  636. % (src_fname, (dst_fname))
  637. )
  638. )
  639. # remove tempo dir 'root' no more needed
  640. shutil.rmtree(root, onerror=onerror)
  641. return dst_fname
  642. def direct_pip_install(
  643. fname,
  644. python_exe=None,
  645. architecture=None,
  646. verbose=False,
  647. install_options=None,
  648. ):
  649. """Direct install via pip !"""
  650. copy_to = osp.dirname(fname)
  651. if python_exe is None:
  652. python_exe = sys.executable
  653. assert osp.isfile(python_exe)
  654. myroot = os.path.dirname(python_exe)
  655. cmd = [python_exe, '-m', 'pip', 'install']
  656. if install_options:
  657. cmd += install_options # typically ['--no-deps']
  658. print('pip install_options', install_options)
  659. cmd += [fname]
  660. if verbose:
  661. subprocess.call(cmd, cwd=myroot)
  662. else:
  663. p = subprocess.Popen(
  664. cmd,
  665. cwd=myroot,
  666. stdout=subprocess.PIPE,
  667. stderr=subprocess.PIPE,
  668. )
  669. stdout, stderr = p.communicate()
  670. the_log = "%s" % stdout + "\n %s" % stderr
  671. if (
  672. ' not find ' in the_log
  673. or ' not found ' in the_log
  674. ):
  675. print("Failed to Install: \n %s \n" % fname)
  676. print("msg: %s" % the_log)
  677. raise RuntimeError
  678. p.stdout.close()
  679. p.stderr.close()
  680. src_fname = fname
  681. if copy_to is None:
  682. return src_fname
  683. else:
  684. if verbose:
  685. print("Installed %s" % src_fname)
  686. return src_fname
  687. def do_script(
  688. this_script,
  689. python_exe=None,
  690. copy_to=None,
  691. architecture=None,
  692. verbose=False,
  693. install_options=None,
  694. ):
  695. """Execute a script (get-pip typically)"""
  696. if python_exe is None:
  697. python_exe = sys.executable
  698. myroot = os.path.dirname(python_exe)
  699. # cmd = [python_exe, myroot + r'\Scripts\pip-script.py', 'install']
  700. cmd = [python_exe]
  701. if install_options:
  702. cmd += install_options # typically ['--no-deps']
  703. print('script install_options', install_options)
  704. if this_script:
  705. cmd += [this_script]
  706. # print('build_wheel', myroot, cmd)
  707. print("Executing ", cmd)
  708. if verbose:
  709. subprocess.call(cmd, cwd=myroot)
  710. else:
  711. p = subprocess.Popen(
  712. cmd,
  713. cwd=myroot,
  714. stdout=subprocess.PIPE,
  715. stderr=subprocess.PIPE,
  716. )
  717. p.communicate()
  718. p.stdout.close()
  719. p.stderr.close()
  720. if verbose:
  721. print("Executed " % cmd)
  722. return 'ok'
  723. if __name__ == '__main__':
  724. print_box("Test")
  725. dname = sys.prefix
  726. print((dname + ':', '\n', get_python_infos(dname)))
  727. # dname = r'E:\winpython\sandbox\python-2.7.3'
  728. # print dname+':', '\n', get_python_infos(dname)
  729. tmpdir = r'D:\Tests\winpython_tests'
  730. if not osp.isdir(tmpdir):
  731. os.mkdir(tmpdir)
  732. print(
  733. (
  734. extract_archive(
  735. osp.join(
  736. r'D:\WinP\bd37',
  737. 'packages.win-amd64',
  738. 'python-3.7.3.amd64.zip',
  739. ),
  740. tmpdir,
  741. )
  742. )
  743. )