util.py 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965
  1. #
  2. # Copyright (C) 2012-2021 The Python Software Foundation.
  3. # See LICENSE.txt and CONTRIBUTORS.txt.
  4. #
  5. import codecs
  6. from collections import deque
  7. import contextlib
  8. import csv
  9. from glob import iglob as std_iglob
  10. import io
  11. import json
  12. import logging
  13. import os
  14. import py_compile
  15. import re
  16. import socket
  17. try:
  18. import ssl
  19. except ImportError: # pragma: no cover
  20. ssl = None
  21. import subprocess
  22. import sys
  23. import tarfile
  24. import tempfile
  25. import textwrap
  26. try:
  27. import threading
  28. except ImportError: # pragma: no cover
  29. import dummy_threading as threading
  30. import time
  31. from . import DistlibException
  32. from .compat import (string_types, text_type, shutil, raw_input, StringIO,
  33. cache_from_source, urlopen, urljoin, httplib, xmlrpclib,
  34. splittype, HTTPHandler, BaseConfigurator, valid_ident,
  35. Container, configparser, URLError, ZipFile, fsdecode,
  36. unquote, urlparse)
  37. logger = logging.getLogger(__name__)
  38. #
  39. # Requirement parsing code as per PEP 508
  40. #
  41. IDENTIFIER = re.compile(r'^([\w\.-]+)\s*')
  42. VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*')
  43. COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*')
  44. MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*')
  45. OR = re.compile(r'^or\b\s*')
  46. AND = re.compile(r'^and\b\s*')
  47. NON_SPACE = re.compile(r'(\S+)\s*')
  48. STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)')
  49. def parse_marker(marker_string):
  50. """
  51. Parse a marker string and return a dictionary containing a marker expression.
  52. The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in
  53. the expression grammar, or strings. A string contained in quotes is to be
  54. interpreted as a literal string, and a string not contained in quotes is a
  55. variable (such as os_name).
  56. """
  57. def marker_var(remaining):
  58. # either identifier, or literal string
  59. m = IDENTIFIER.match(remaining)
  60. if m:
  61. result = m.groups()[0]
  62. remaining = remaining[m.end():]
  63. elif not remaining:
  64. raise SyntaxError('unexpected end of input')
  65. else:
  66. q = remaining[0]
  67. if q not in '\'"':
  68. raise SyntaxError('invalid expression: %s' % remaining)
  69. oq = '\'"'.replace(q, '')
  70. remaining = remaining[1:]
  71. parts = [q]
  72. while remaining:
  73. # either a string chunk, or oq, or q to terminate
  74. if remaining[0] == q:
  75. break
  76. elif remaining[0] == oq:
  77. parts.append(oq)
  78. remaining = remaining[1:]
  79. else:
  80. m = STRING_CHUNK.match(remaining)
  81. if not m:
  82. raise SyntaxError('error in string literal: %s' % remaining)
  83. parts.append(m.groups()[0])
  84. remaining = remaining[m.end():]
  85. else:
  86. s = ''.join(parts)
  87. raise SyntaxError('unterminated string: %s' % s)
  88. parts.append(q)
  89. result = ''.join(parts)
  90. remaining = remaining[1:].lstrip() # skip past closing quote
  91. return result, remaining
  92. def marker_expr(remaining):
  93. if remaining and remaining[0] == '(':
  94. result, remaining = marker(remaining[1:].lstrip())
  95. if remaining[0] != ')':
  96. raise SyntaxError('unterminated parenthesis: %s' % remaining)
  97. remaining = remaining[1:].lstrip()
  98. else:
  99. lhs, remaining = marker_var(remaining)
  100. while remaining:
  101. m = MARKER_OP.match(remaining)
  102. if not m:
  103. break
  104. op = m.groups()[0]
  105. remaining = remaining[m.end():]
  106. rhs, remaining = marker_var(remaining)
  107. lhs = {'op': op, 'lhs': lhs, 'rhs': rhs}
  108. result = lhs
  109. return result, remaining
  110. def marker_and(remaining):
  111. lhs, remaining = marker_expr(remaining)
  112. while remaining:
  113. m = AND.match(remaining)
  114. if not m:
  115. break
  116. remaining = remaining[m.end():]
  117. rhs, remaining = marker_expr(remaining)
  118. lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs}
  119. return lhs, remaining
  120. def marker(remaining):
  121. lhs, remaining = marker_and(remaining)
  122. while remaining:
  123. m = OR.match(remaining)
  124. if not m:
  125. break
  126. remaining = remaining[m.end():]
  127. rhs, remaining = marker_and(remaining)
  128. lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs}
  129. return lhs, remaining
  130. return marker(marker_string)
  131. def parse_requirement(req):
  132. """
  133. Parse a requirement passed in as a string. Return a Container
  134. whose attributes contain the various parts of the requirement.
  135. """
  136. remaining = req.strip()
  137. if not remaining or remaining.startswith('#'):
  138. return None
  139. m = IDENTIFIER.match(remaining)
  140. if not m:
  141. raise SyntaxError('name expected: %s' % remaining)
  142. distname = m.groups()[0]
  143. remaining = remaining[m.end():]
  144. extras = mark_expr = versions = uri = None
  145. if remaining and remaining[0] == '[':
  146. i = remaining.find(']', 1)
  147. if i < 0:
  148. raise SyntaxError('unterminated extra: %s' % remaining)
  149. s = remaining[1:i]
  150. remaining = remaining[i + 1:].lstrip()
  151. extras = []
  152. while s:
  153. m = IDENTIFIER.match(s)
  154. if not m:
  155. raise SyntaxError('malformed extra: %s' % s)
  156. extras.append(m.groups()[0])
  157. s = s[m.end():]
  158. if not s:
  159. break
  160. if s[0] != ',':
  161. raise SyntaxError('comma expected in extras: %s' % s)
  162. s = s[1:].lstrip()
  163. if not extras:
  164. extras = None
  165. if remaining:
  166. if remaining[0] == '@':
  167. # it's a URI
  168. remaining = remaining[1:].lstrip()
  169. m = NON_SPACE.match(remaining)
  170. if not m:
  171. raise SyntaxError('invalid URI: %s' % remaining)
  172. uri = m.groups()[0]
  173. t = urlparse(uri)
  174. # there are issues with Python and URL parsing, so this test
  175. # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
  176. # always parse invalid URLs correctly - it should raise
  177. # exceptions for malformed URLs
  178. if not (t.scheme and t.netloc):
  179. raise SyntaxError('Invalid URL: %s' % uri)
  180. remaining = remaining[m.end():].lstrip()
  181. else:
  182. def get_versions(ver_remaining):
  183. """
  184. Return a list of operator, version tuples if any are
  185. specified, else None.
  186. """
  187. m = COMPARE_OP.match(ver_remaining)
  188. versions = None
  189. if m:
  190. versions = []
  191. while True:
  192. op = m.groups()[0]
  193. ver_remaining = ver_remaining[m.end():]
  194. m = VERSION_IDENTIFIER.match(ver_remaining)
  195. if not m:
  196. raise SyntaxError('invalid version: %s' % ver_remaining)
  197. v = m.groups()[0]
  198. versions.append((op, v))
  199. ver_remaining = ver_remaining[m.end():]
  200. if not ver_remaining or ver_remaining[0] != ',':
  201. break
  202. ver_remaining = ver_remaining[1:].lstrip()
  203. m = COMPARE_OP.match(ver_remaining)
  204. if not m:
  205. raise SyntaxError('invalid constraint: %s' % ver_remaining)
  206. if not versions:
  207. versions = None
  208. return versions, ver_remaining
  209. if remaining[0] != '(':
  210. versions, remaining = get_versions(remaining)
  211. else:
  212. i = remaining.find(')', 1)
  213. if i < 0:
  214. raise SyntaxError('unterminated parenthesis: %s' % remaining)
  215. s = remaining[1:i]
  216. remaining = remaining[i + 1:].lstrip()
  217. # As a special diversion from PEP 508, allow a version number
  218. # a.b.c in parentheses as a synonym for ~= a.b.c (because this
  219. # is allowed in earlier PEPs)
  220. if COMPARE_OP.match(s):
  221. versions, _ = get_versions(s)
  222. else:
  223. m = VERSION_IDENTIFIER.match(s)
  224. if not m:
  225. raise SyntaxError('invalid constraint: %s' % s)
  226. v = m.groups()[0]
  227. s = s[m.end():].lstrip()
  228. if s:
  229. raise SyntaxError('invalid constraint: %s' % s)
  230. versions = [('~=', v)]
  231. if remaining:
  232. if remaining[0] != ';':
  233. raise SyntaxError('invalid requirement: %s' % remaining)
  234. remaining = remaining[1:].lstrip()
  235. mark_expr, remaining = parse_marker(remaining)
  236. if remaining and remaining[0] != '#':
  237. raise SyntaxError('unexpected trailing data: %s' % remaining)
  238. if not versions:
  239. rs = distname
  240. else:
  241. rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions]))
  242. return Container(name=distname, extras=extras, constraints=versions,
  243. marker=mark_expr, url=uri, requirement=rs)
  244. def get_resources_dests(resources_root, rules):
  245. """Find destinations for resources files"""
  246. def get_rel_path(root, path):
  247. # normalizes and returns a lstripped-/-separated path
  248. root = root.replace(os.path.sep, '/')
  249. path = path.replace(os.path.sep, '/')
  250. assert path.startswith(root)
  251. return path[len(root):].lstrip('/')
  252. destinations = {}
  253. for base, suffix, dest in rules:
  254. prefix = os.path.join(resources_root, base)
  255. for abs_base in iglob(prefix):
  256. abs_glob = os.path.join(abs_base, suffix)
  257. for abs_path in iglob(abs_glob):
  258. resource_file = get_rel_path(resources_root, abs_path)
  259. if dest is None: # remove the entry if it was here
  260. destinations.pop(resource_file, None)
  261. else:
  262. rel_path = get_rel_path(abs_base, abs_path)
  263. rel_dest = dest.replace(os.path.sep, '/').rstrip('/')
  264. destinations[resource_file] = rel_dest + '/' + rel_path
  265. return destinations
  266. def in_venv():
  267. if hasattr(sys, 'real_prefix'):
  268. # virtualenv venvs
  269. result = True
  270. else:
  271. # PEP 405 venvs
  272. result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix)
  273. return result
  274. def get_executable():
  275. # The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as
  276. # changes to the stub launcher mean that sys.executable always points
  277. # to the stub on OS X
  278. # if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
  279. # in os.environ):
  280. # result = os.environ['__PYVENV_LAUNCHER__']
  281. # else:
  282. # result = sys.executable
  283. # return result
  284. # Avoid normcasing: see issue #143
  285. # result = os.path.normcase(sys.executable)
  286. result = sys.executable
  287. if not isinstance(result, text_type):
  288. result = fsdecode(result)
  289. return result
  290. def proceed(prompt, allowed_chars, error_prompt=None, default=None):
  291. p = prompt
  292. while True:
  293. s = raw_input(p)
  294. p = prompt
  295. if not s and default:
  296. s = default
  297. if s:
  298. c = s[0].lower()
  299. if c in allowed_chars:
  300. break
  301. if error_prompt:
  302. p = '%c: %s\n%s' % (c, error_prompt, prompt)
  303. return c
  304. def extract_by_key(d, keys):
  305. if isinstance(keys, string_types):
  306. keys = keys.split()
  307. result = {}
  308. for key in keys:
  309. if key in d:
  310. result[key] = d[key]
  311. return result
  312. def read_exports(stream):
  313. if sys.version_info[0] >= 3:
  314. # needs to be a text stream
  315. stream = codecs.getreader('utf-8')(stream)
  316. # Try to load as JSON, falling back on legacy format
  317. data = stream.read()
  318. stream = StringIO(data)
  319. try:
  320. jdata = json.load(stream)
  321. result = jdata['extensions']['python.exports']['exports']
  322. for group, entries in result.items():
  323. for k, v in entries.items():
  324. s = '%s = %s' % (k, v)
  325. entry = get_export_entry(s)
  326. assert entry is not None
  327. entries[k] = entry
  328. return result
  329. except Exception:
  330. stream.seek(0, 0)
  331. def read_stream(cp, stream):
  332. if hasattr(cp, 'read_file'):
  333. cp.read_file(stream)
  334. else:
  335. cp.readfp(stream)
  336. cp = configparser.ConfigParser()
  337. try:
  338. read_stream(cp, stream)
  339. except configparser.MissingSectionHeaderError:
  340. stream.close()
  341. data = textwrap.dedent(data)
  342. stream = StringIO(data)
  343. read_stream(cp, stream)
  344. result = {}
  345. for key in cp.sections():
  346. result[key] = entries = {}
  347. for name, value in cp.items(key):
  348. s = '%s = %s' % (name, value)
  349. entry = get_export_entry(s)
  350. assert entry is not None
  351. #entry.dist = self
  352. entries[name] = entry
  353. return result
  354. def write_exports(exports, stream):
  355. if sys.version_info[0] >= 3:
  356. # needs to be a text stream
  357. stream = codecs.getwriter('utf-8')(stream)
  358. cp = configparser.ConfigParser()
  359. for k, v in exports.items():
  360. # TODO check k, v for valid values
  361. cp.add_section(k)
  362. for entry in v.values():
  363. if entry.suffix is None:
  364. s = entry.prefix
  365. else:
  366. s = '%s:%s' % (entry.prefix, entry.suffix)
  367. if entry.flags:
  368. s = '%s [%s]' % (s, ', '.join(entry.flags))
  369. cp.set(k, entry.name, s)
  370. cp.write(stream)
  371. @contextlib.contextmanager
  372. def tempdir():
  373. td = tempfile.mkdtemp()
  374. try:
  375. yield td
  376. finally:
  377. shutil.rmtree(td)
  378. @contextlib.contextmanager
  379. def chdir(d):
  380. cwd = os.getcwd()
  381. try:
  382. os.chdir(d)
  383. yield
  384. finally:
  385. os.chdir(cwd)
  386. @contextlib.contextmanager
  387. def socket_timeout(seconds=15):
  388. cto = socket.getdefaulttimeout()
  389. try:
  390. socket.setdefaulttimeout(seconds)
  391. yield
  392. finally:
  393. socket.setdefaulttimeout(cto)
  394. class cached_property(object):
  395. def __init__(self, func):
  396. self.func = func
  397. #for attr in ('__name__', '__module__', '__doc__'):
  398. # setattr(self, attr, getattr(func, attr, None))
  399. def __get__(self, obj, cls=None):
  400. if obj is None:
  401. return self
  402. value = self.func(obj)
  403. object.__setattr__(obj, self.func.__name__, value)
  404. #obj.__dict__[self.func.__name__] = value = self.func(obj)
  405. return value
  406. def convert_path(pathname):
  407. """Return 'pathname' as a name that will work on the native filesystem.
  408. The path is split on '/' and put back together again using the current
  409. directory separator. Needed because filenames in the setup script are
  410. always supplied in Unix style, and have to be converted to the local
  411. convention before we can actually use them in the filesystem. Raises
  412. ValueError on non-Unix-ish systems if 'pathname' either starts or
  413. ends with a slash.
  414. """
  415. if os.sep == '/':
  416. return pathname
  417. if not pathname:
  418. return pathname
  419. if pathname[0] == '/':
  420. raise ValueError("path '%s' cannot be absolute" % pathname)
  421. if pathname[-1] == '/':
  422. raise ValueError("path '%s' cannot end with '/'" % pathname)
  423. paths = pathname.split('/')
  424. while os.curdir in paths:
  425. paths.remove(os.curdir)
  426. if not paths:
  427. return os.curdir
  428. return os.path.join(*paths)
  429. class FileOperator(object):
  430. def __init__(self, dry_run=False):
  431. self.dry_run = dry_run
  432. self.ensured = set()
  433. self._init_record()
  434. def _init_record(self):
  435. self.record = False
  436. self.files_written = set()
  437. self.dirs_created = set()
  438. def record_as_written(self, path):
  439. if self.record:
  440. self.files_written.add(path)
  441. def newer(self, source, target):
  442. """Tell if the target is newer than the source.
  443. Returns true if 'source' exists and is more recently modified than
  444. 'target', or if 'source' exists and 'target' doesn't.
  445. Returns false if both exist and 'target' is the same age or younger
  446. than 'source'. Raise PackagingFileError if 'source' does not exist.
  447. Note that this test is not very accurate: files created in the same
  448. second will have the same "age".
  449. """
  450. if not os.path.exists(source):
  451. raise DistlibException("file '%r' does not exist" %
  452. os.path.abspath(source))
  453. if not os.path.exists(target):
  454. return True
  455. return os.stat(source).st_mtime > os.stat(target).st_mtime
  456. def copy_file(self, infile, outfile, check=True):
  457. """Copy a file respecting dry-run and force flags.
  458. """
  459. self.ensure_dir(os.path.dirname(outfile))
  460. logger.info('Copying %s to %s', infile, outfile)
  461. if not self.dry_run:
  462. msg = None
  463. if check:
  464. if os.path.islink(outfile):
  465. msg = '%s is a symlink' % outfile
  466. elif os.path.exists(outfile) and not os.path.isfile(outfile):
  467. msg = '%s is a non-regular file' % outfile
  468. if msg:
  469. raise ValueError(msg + ' which would be overwritten')
  470. shutil.copyfile(infile, outfile)
  471. self.record_as_written(outfile)
  472. def copy_stream(self, instream, outfile, encoding=None):
  473. assert not os.path.isdir(outfile)
  474. self.ensure_dir(os.path.dirname(outfile))
  475. logger.info('Copying stream %s to %s', instream, outfile)
  476. if not self.dry_run:
  477. if encoding is None:
  478. outstream = open(outfile, 'wb')
  479. else:
  480. outstream = codecs.open(outfile, 'w', encoding=encoding)
  481. try:
  482. shutil.copyfileobj(instream, outstream)
  483. finally:
  484. outstream.close()
  485. self.record_as_written(outfile)
  486. def write_binary_file(self, path, data):
  487. self.ensure_dir(os.path.dirname(path))
  488. if not self.dry_run:
  489. if os.path.exists(path):
  490. os.remove(path)
  491. with open(path, 'wb') as f:
  492. f.write(data)
  493. self.record_as_written(path)
  494. def write_text_file(self, path, data, encoding):
  495. self.write_binary_file(path, data.encode(encoding))
  496. def set_mode(self, bits, mask, files):
  497. if os.name == 'posix' or (os.name == 'java' and os._name == 'posix'):
  498. # Set the executable bits (owner, group, and world) on
  499. # all the files specified.
  500. for f in files:
  501. if self.dry_run:
  502. logger.info("changing mode of %s", f)
  503. else:
  504. mode = (os.stat(f).st_mode | bits) & mask
  505. logger.info("changing mode of %s to %o", f, mode)
  506. os.chmod(f, mode)
  507. set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f)
  508. def ensure_dir(self, path):
  509. path = os.path.abspath(path)
  510. if path not in self.ensured and not os.path.exists(path):
  511. self.ensured.add(path)
  512. d, f = os.path.split(path)
  513. self.ensure_dir(d)
  514. logger.info('Creating %s' % path)
  515. if not self.dry_run:
  516. os.mkdir(path)
  517. if self.record:
  518. self.dirs_created.add(path)
  519. def byte_compile(self, path, optimize=False, force=False, prefix=None, hashed_invalidation=False):
  520. dpath = cache_from_source(path, not optimize)
  521. logger.info('Byte-compiling %s to %s', path, dpath)
  522. if not self.dry_run:
  523. if force or self.newer(path, dpath):
  524. if not prefix:
  525. diagpath = None
  526. else:
  527. assert path.startswith(prefix)
  528. diagpath = path[len(prefix):]
  529. compile_kwargs = {}
  530. if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'):
  531. compile_kwargs['invalidation_mode'] = py_compile.PycInvalidationMode.CHECKED_HASH
  532. py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error
  533. self.record_as_written(dpath)
  534. return dpath
  535. def ensure_removed(self, path):
  536. if os.path.exists(path):
  537. if os.path.isdir(path) and not os.path.islink(path):
  538. logger.debug('Removing directory tree at %s', path)
  539. if not self.dry_run:
  540. shutil.rmtree(path)
  541. if self.record:
  542. if path in self.dirs_created:
  543. self.dirs_created.remove(path)
  544. else:
  545. if os.path.islink(path):
  546. s = 'link'
  547. else:
  548. s = 'file'
  549. logger.debug('Removing %s %s', s, path)
  550. if not self.dry_run:
  551. os.remove(path)
  552. if self.record:
  553. if path in self.files_written:
  554. self.files_written.remove(path)
  555. def is_writable(self, path):
  556. result = False
  557. while not result:
  558. if os.path.exists(path):
  559. result = os.access(path, os.W_OK)
  560. break
  561. parent = os.path.dirname(path)
  562. if parent == path:
  563. break
  564. path = parent
  565. return result
  566. def commit(self):
  567. """
  568. Commit recorded changes, turn off recording, return
  569. changes.
  570. """
  571. assert self.record
  572. result = self.files_written, self.dirs_created
  573. self._init_record()
  574. return result
  575. def rollback(self):
  576. if not self.dry_run:
  577. for f in list(self.files_written):
  578. if os.path.exists(f):
  579. os.remove(f)
  580. # dirs should all be empty now, except perhaps for
  581. # __pycache__ subdirs
  582. # reverse so that subdirs appear before their parents
  583. dirs = sorted(self.dirs_created, reverse=True)
  584. for d in dirs:
  585. flist = os.listdir(d)
  586. if flist:
  587. assert flist == ['__pycache__']
  588. sd = os.path.join(d, flist[0])
  589. os.rmdir(sd)
  590. os.rmdir(d) # should fail if non-empty
  591. self._init_record()
  592. def resolve(module_name, dotted_path):
  593. if module_name in sys.modules:
  594. mod = sys.modules[module_name]
  595. else:
  596. mod = __import__(module_name)
  597. if dotted_path is None:
  598. result = mod
  599. else:
  600. parts = dotted_path.split('.')
  601. result = getattr(mod, parts.pop(0))
  602. for p in parts:
  603. result = getattr(result, p)
  604. return result
  605. class ExportEntry(object):
  606. def __init__(self, name, prefix, suffix, flags):
  607. self.name = name
  608. self.prefix = prefix
  609. self.suffix = suffix
  610. self.flags = flags
  611. @cached_property
  612. def value(self):
  613. return resolve(self.prefix, self.suffix)
  614. def __repr__(self): # pragma: no cover
  615. return '<ExportEntry %s = %s:%s %s>' % (self.name, self.prefix,
  616. self.suffix, self.flags)
  617. def __eq__(self, other):
  618. if not isinstance(other, ExportEntry):
  619. result = False
  620. else:
  621. result = (self.name == other.name and
  622. self.prefix == other.prefix and
  623. self.suffix == other.suffix and
  624. self.flags == other.flags)
  625. return result
  626. __hash__ = object.__hash__
  627. ENTRY_RE = re.compile(r'''(?P<name>(\w|[-.+])+)
  628. \s*=\s*(?P<callable>(\w+)([:\.]\w+)*)
  629. \s*(\[\s*(?P<flags>[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])?
  630. ''', re.VERBOSE)
  631. def get_export_entry(specification):
  632. m = ENTRY_RE.search(specification)
  633. if not m:
  634. result = None
  635. if '[' in specification or ']' in specification:
  636. raise DistlibException("Invalid specification "
  637. "'%s'" % specification)
  638. else:
  639. d = m.groupdict()
  640. name = d['name']
  641. path = d['callable']
  642. colons = path.count(':')
  643. if colons == 0:
  644. prefix, suffix = path, None
  645. else:
  646. if colons != 1:
  647. raise DistlibException("Invalid specification "
  648. "'%s'" % specification)
  649. prefix, suffix = path.split(':')
  650. flags = d['flags']
  651. if flags is None:
  652. if '[' in specification or ']' in specification:
  653. raise DistlibException("Invalid specification "
  654. "'%s'" % specification)
  655. flags = []
  656. else:
  657. flags = [f.strip() for f in flags.split(',')]
  658. result = ExportEntry(name, prefix, suffix, flags)
  659. return result
  660. def get_cache_base(suffix=None):
  661. """
  662. Return the default base location for distlib caches. If the directory does
  663. not exist, it is created. Use the suffix provided for the base directory,
  664. and default to '.distlib' if it isn't provided.
  665. On Windows, if LOCALAPPDATA is defined in the environment, then it is
  666. assumed to be a directory, and will be the parent directory of the result.
  667. On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home
  668. directory - using os.expanduser('~') - will be the parent directory of
  669. the result.
  670. The result is just the directory '.distlib' in the parent directory as
  671. determined above, or with the name specified with ``suffix``.
  672. """
  673. if suffix is None:
  674. suffix = '.distlib'
  675. if os.name == 'nt' and 'LOCALAPPDATA' in os.environ:
  676. result = os.path.expandvars('$localappdata')
  677. else:
  678. # Assume posix, or old Windows
  679. result = os.path.expanduser('~')
  680. # we use 'isdir' instead of 'exists', because we want to
  681. # fail if there's a file with that name
  682. if os.path.isdir(result):
  683. usable = os.access(result, os.W_OK)
  684. if not usable:
  685. logger.warning('Directory exists but is not writable: %s', result)
  686. else:
  687. try:
  688. os.makedirs(result)
  689. usable = True
  690. except OSError:
  691. logger.warning('Unable to create %s', result, exc_info=True)
  692. usable = False
  693. if not usable:
  694. result = tempfile.mkdtemp()
  695. logger.warning('Default location unusable, using %s', result)
  696. return os.path.join(result, suffix)
  697. def path_to_cache_dir(path):
  698. """
  699. Convert an absolute path to a directory name for use in a cache.
  700. The algorithm used is:
  701. #. On Windows, any ``':'`` in the drive is replaced with ``'---'``.
  702. #. Any occurrence of ``os.sep`` is replaced with ``'--'``.
  703. #. ``'.cache'`` is appended.
  704. """
  705. d, p = os.path.splitdrive(os.path.abspath(path))
  706. if d:
  707. d = d.replace(':', '---')
  708. p = p.replace(os.sep, '--')
  709. return d + p + '.cache'
  710. def ensure_slash(s):
  711. if not s.endswith('/'):
  712. return s + '/'
  713. return s
  714. def parse_credentials(netloc):
  715. username = password = None
  716. if '@' in netloc:
  717. prefix, netloc = netloc.rsplit('@', 1)
  718. if ':' not in prefix:
  719. username = prefix
  720. else:
  721. username, password = prefix.split(':', 1)
  722. if username:
  723. username = unquote(username)
  724. if password:
  725. password = unquote(password)
  726. return username, password, netloc
  727. def get_process_umask():
  728. result = os.umask(0o22)
  729. os.umask(result)
  730. return result
  731. def is_string_sequence(seq):
  732. result = True
  733. i = None
  734. for i, s in enumerate(seq):
  735. if not isinstance(s, string_types):
  736. result = False
  737. break
  738. assert i is not None
  739. return result
  740. PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-'
  741. '([a-z0-9_.+-]+)', re.I)
  742. PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)')
  743. def split_filename(filename, project_name=None):
  744. """
  745. Extract name, version, python version from a filename (no extension)
  746. Return name, version, pyver or None
  747. """
  748. result = None
  749. pyver = None
  750. filename = unquote(filename).replace(' ', '-')
  751. m = PYTHON_VERSION.search(filename)
  752. if m:
  753. pyver = m.group(1)
  754. filename = filename[:m.start()]
  755. if project_name and len(filename) > len(project_name) + 1:
  756. m = re.match(re.escape(project_name) + r'\b', filename)
  757. if m:
  758. n = m.end()
  759. result = filename[:n], filename[n + 1:], pyver
  760. if result is None:
  761. m = PROJECT_NAME_AND_VERSION.match(filename)
  762. if m:
  763. result = m.group(1), m.group(3), pyver
  764. return result
  765. # Allow spaces in name because of legacy dists like "Twisted Core"
  766. NAME_VERSION_RE = re.compile(r'(?P<name>[\w .-]+)\s*'
  767. r'\(\s*(?P<ver>[^\s)]+)\)$')
  768. def parse_name_and_version(p):
  769. """
  770. A utility method used to get name and version from a string.
  771. From e.g. a Provides-Dist value.
  772. :param p: A value in a form 'foo (1.0)'
  773. :return: The name and version as a tuple.
  774. """
  775. m = NAME_VERSION_RE.match(p)
  776. if not m:
  777. raise DistlibException('Ill-formed name/version string: \'%s\'' % p)
  778. d = m.groupdict()
  779. return d['name'].strip().lower(), d['ver']
  780. def get_extras(requested, available):
  781. result = set()
  782. requested = set(requested or [])
  783. available = set(available or [])
  784. if '*' in requested:
  785. requested.remove('*')
  786. result |= available
  787. for r in requested:
  788. if r == '-':
  789. result.add(r)
  790. elif r.startswith('-'):
  791. unwanted = r[1:]
  792. if unwanted not in available:
  793. logger.warning('undeclared extra: %s' % unwanted)
  794. if unwanted in result:
  795. result.remove(unwanted)
  796. else:
  797. if r not in available:
  798. logger.warning('undeclared extra: %s' % r)
  799. result.add(r)
  800. return result
  801. #
  802. # Extended metadata functionality
  803. #
  804. def _get_external_data(url):
  805. result = {}
  806. try:
  807. # urlopen might fail if it runs into redirections,
  808. # because of Python issue #13696. Fixed in locators
  809. # using a custom redirect handler.
  810. resp = urlopen(url)
  811. headers = resp.info()
  812. ct = headers.get('Content-Type')
  813. if not ct.startswith('application/json'):
  814. logger.debug('Unexpected response for JSON request: %s', ct)
  815. else:
  816. reader = codecs.getreader('utf-8')(resp)
  817. #data = reader.read().decode('utf-8')
  818. #result = json.loads(data)
  819. result = json.load(reader)
  820. except Exception as e:
  821. logger.exception('Failed to get external data for %s: %s', url, e)
  822. return result
  823. _external_data_base_url = 'https://www.red-dove.com/pypi/projects/'
  824. def get_project_data(name):
  825. url = '%s/%s/project.json' % (name[0].upper(), name)
  826. url = urljoin(_external_data_base_url, url)
  827. result = _get_external_data(url)
  828. return result
  829. def get_package_data(name, version):
  830. url = '%s/%s/package-%s.json' % (name[0].upper(), name, version)
  831. url = urljoin(_external_data_base_url, url)
  832. return _get_external_data(url)
  833. class Cache(object):
  834. """
  835. A class implementing a cache for resources that need to live in the file system
  836. e.g. shared libraries. This class was moved from resources to here because it
  837. could be used by other modules, e.g. the wheel module.
  838. """
  839. def __init__(self, base):
  840. """
  841. Initialise an instance.
  842. :param base: The base directory where the cache should be located.
  843. """
  844. # we use 'isdir' instead of 'exists', because we want to
  845. # fail if there's a file with that name
  846. if not os.path.isdir(base): # pragma: no cover
  847. os.makedirs(base)
  848. if (os.stat(base).st_mode & 0o77) != 0:
  849. logger.warning('Directory \'%s\' is not private', base)
  850. self.base = os.path.abspath(os.path.normpath(base))
  851. def prefix_to_dir(self, prefix):
  852. """
  853. Converts a resource prefix to a directory name in the cache.
  854. """
  855. return path_to_cache_dir(prefix)
  856. def clear(self):
  857. """
  858. Clear the cache.
  859. """
  860. not_removed = []
  861. for fn in os.listdir(self.base):
  862. fn = os.path.join(self.base, fn)
  863. try:
  864. if os.path.islink(fn) or os.path.isfile(fn):
  865. os.remove(fn)
  866. elif os.path.isdir(fn):
  867. shutil.rmtree(fn)
  868. except Exception:
  869. not_removed.append(fn)
  870. return not_removed
  871. class EventMixin(object):
  872. """
  873. A very simple publish/subscribe system.
  874. """
  875. def __init__(self):
  876. self._subscribers = {}
  877. def add(self, event, subscriber, append=True):
  878. """
  879. Add a subscriber for an event.
  880. :param event: The name of an event.
  881. :param subscriber: The subscriber to be added (and called when the
  882. event is published).
  883. :param append: Whether to append or prepend the subscriber to an
  884. existing subscriber list for the event.
  885. """
  886. subs = self._subscribers
  887. if event not in subs:
  888. subs[event] = deque([subscriber])
  889. else:
  890. sq = subs[event]
  891. if append:
  892. sq.append(subscriber)
  893. else:
  894. sq.appendleft(subscriber)
  895. def remove(self, event, subscriber):
  896. """
  897. Remove a subscriber for an event.
  898. :param event: The name of an event.
  899. :param subscriber: The subscriber to be removed.
  900. """
  901. subs = self._subscribers
  902. if event not in subs:
  903. raise ValueError('No subscribers: %r' % event)
  904. subs[event].remove(subscriber)
  905. def get_subscribers(self, event):
  906. """
  907. Return an iterator for the subscribers for an event.
  908. :param event: The event to return subscribers for.
  909. """
  910. return iter(self._subscribers.get(event, ()))
  911. def publish(self, event, *args, **kwargs):
  912. """
  913. Publish a event and return a list of values returned by its
  914. subscribers.
  915. :param event: The event to publish.
  916. :param args: The positional arguments to pass to the event's
  917. subscribers.
  918. :param kwargs: The keyword arguments to pass to the event's
  919. subscribers.
  920. """
  921. result = []
  922. for subscriber in self.get_subscribers(event):
  923. try:
  924. value = subscriber(event, *args, **kwargs)
  925. except Exception:
  926. logger.exception('Exception during event publication')
  927. value = None
  928. result.append(value)
  929. logger.debug('publish %s: args = %s, kwargs = %s, result = %s',
  930. event, args, kwargs, result)
  931. return result
  932. #
  933. # Simple sequencing
  934. #
  935. class Sequencer(object):
  936. def __init__(self):
  937. self._preds = {}
  938. self._succs = {}
  939. self._nodes = set() # nodes with no preds/succs
  940. def add_node(self, node):
  941. self._nodes.add(node)
  942. def remove_node(self, node, edges=False):
  943. if node in self._nodes:
  944. self._nodes.remove(node)
  945. if edges:
  946. for p in set(self._preds.get(node, ())):
  947. self.remove(p, node)
  948. for s in set(self._succs.get(node, ())):
  949. self.remove(node, s)
  950. # Remove empties
  951. for k, v in list(self._preds.items()):
  952. if not v:
  953. del self._preds[k]
  954. for k, v in list(self._succs.items()):
  955. if not v:
  956. del self._succs[k]
  957. def add(self, pred, succ):
  958. assert pred != succ
  959. self._preds.setdefault(succ, set()).add(pred)
  960. self._succs.setdefault(pred, set()).add(succ)
  961. def remove(self, pred, succ):
  962. assert pred != succ
  963. try:
  964. preds = self._preds[succ]
  965. succs = self._succs[pred]
  966. except KeyError: # pragma: no cover
  967. raise ValueError('%r not a successor of anything' % succ)
  968. try:
  969. preds.remove(pred)
  970. succs.remove(succ)
  971. except KeyError: # pragma: no cover
  972. raise ValueError('%r not a successor of %r' % (succ, pred))
  973. def is_step(self, step):
  974. return (step in self._preds or step in self._succs or
  975. step in self._nodes)
  976. def get_steps(self, final):
  977. if not self.is_step(final):
  978. raise ValueError('Unknown: %r' % final)
  979. result = []
  980. todo = []
  981. seen = set()
  982. todo.append(final)
  983. while todo:
  984. step = todo.pop(0)
  985. if step in seen:
  986. # if a step was already seen,
  987. # move it to the end (so it will appear earlier
  988. # when reversed on return) ... but not for the
  989. # final step, as that would be confusing for
  990. # users
  991. if step != final:
  992. result.remove(step)
  993. result.append(step)
  994. else:
  995. seen.add(step)
  996. result.append(step)
  997. preds = self._preds.get(step, ())
  998. todo.extend(preds)
  999. return reversed(result)
  1000. @property
  1001. def strong_connections(self):
  1002. #http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
  1003. index_counter = [0]
  1004. stack = []
  1005. lowlinks = {}
  1006. index = {}
  1007. result = []
  1008. graph = self._succs
  1009. def strongconnect(node):
  1010. # set the depth index for this node to the smallest unused index
  1011. index[node] = index_counter[0]
  1012. lowlinks[node] = index_counter[0]
  1013. index_counter[0] += 1
  1014. stack.append(node)
  1015. # Consider successors
  1016. try:
  1017. successors = graph[node]
  1018. except Exception:
  1019. successors = []
  1020. for successor in successors:
  1021. if successor not in lowlinks:
  1022. # Successor has not yet been visited
  1023. strongconnect(successor)
  1024. lowlinks[node] = min(lowlinks[node],lowlinks[successor])
  1025. elif successor in stack:
  1026. # the successor is in the stack and hence in the current
  1027. # strongly connected component (SCC)
  1028. lowlinks[node] = min(lowlinks[node],index[successor])
  1029. # If `node` is a root node, pop the stack and generate an SCC
  1030. if lowlinks[node] == index[node]:
  1031. connected_component = []
  1032. while True:
  1033. successor = stack.pop()
  1034. connected_component.append(successor)
  1035. if successor == node: break
  1036. component = tuple(connected_component)
  1037. # storing the result
  1038. result.append(component)
  1039. for node in graph:
  1040. if node not in lowlinks:
  1041. strongconnect(node)
  1042. return result
  1043. @property
  1044. def dot(self):
  1045. result = ['digraph G {']
  1046. for succ in self._preds:
  1047. preds = self._preds[succ]
  1048. for pred in preds:
  1049. result.append(' %s -> %s;' % (pred, succ))
  1050. for node in self._nodes:
  1051. result.append(' %s;' % node)
  1052. result.append('}')
  1053. return '\n'.join(result)
  1054. #
  1055. # Unarchiving functionality for zip, tar, tgz, tbz, whl
  1056. #
  1057. ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip',
  1058. '.tgz', '.tbz', '.whl')
  1059. def unarchive(archive_filename, dest_dir, format=None, check=True):
  1060. def check_path(path):
  1061. if not isinstance(path, text_type):
  1062. path = path.decode('utf-8')
  1063. p = os.path.abspath(os.path.join(dest_dir, path))
  1064. if not p.startswith(dest_dir) or p[plen] != os.sep:
  1065. raise ValueError('path outside destination: %r' % p)
  1066. dest_dir = os.path.abspath(dest_dir)
  1067. plen = len(dest_dir)
  1068. archive = None
  1069. if format is None:
  1070. if archive_filename.endswith(('.zip', '.whl')):
  1071. format = 'zip'
  1072. elif archive_filename.endswith(('.tar.gz', '.tgz')):
  1073. format = 'tgz'
  1074. mode = 'r:gz'
  1075. elif archive_filename.endswith(('.tar.bz2', '.tbz')):
  1076. format = 'tbz'
  1077. mode = 'r:bz2'
  1078. elif archive_filename.endswith('.tar'):
  1079. format = 'tar'
  1080. mode = 'r'
  1081. else: # pragma: no cover
  1082. raise ValueError('Unknown format for %r' % archive_filename)
  1083. try:
  1084. if format == 'zip':
  1085. archive = ZipFile(archive_filename, 'r')
  1086. if check:
  1087. names = archive.namelist()
  1088. for name in names:
  1089. check_path(name)
  1090. else:
  1091. archive = tarfile.open(archive_filename, mode)
  1092. if check:
  1093. names = archive.getnames()
  1094. for name in names:
  1095. check_path(name)
  1096. if format != 'zip' and sys.version_info[0] < 3:
  1097. # See Python issue 17153. If the dest path contains Unicode,
  1098. # tarfile extraction fails on Python 2.x if a member path name
  1099. # contains non-ASCII characters - it leads to an implicit
  1100. # bytes -> unicode conversion using ASCII to decode.
  1101. for tarinfo in archive.getmembers():
  1102. if not isinstance(tarinfo.name, text_type):
  1103. tarinfo.name = tarinfo.name.decode('utf-8')
  1104. archive.extractall(dest_dir)
  1105. finally:
  1106. if archive:
  1107. archive.close()
  1108. def zip_dir(directory):
  1109. """zip a directory tree into a BytesIO object"""
  1110. result = io.BytesIO()
  1111. dlen = len(directory)
  1112. with ZipFile(result, "w") as zf:
  1113. for root, dirs, files in os.walk(directory):
  1114. for name in files:
  1115. full = os.path.join(root, name)
  1116. rel = root[dlen:]
  1117. dest = os.path.join(rel, name)
  1118. zf.write(full, dest)
  1119. return result
  1120. #
  1121. # Simple progress bar
  1122. #
  1123. UNITS = ('', 'K', 'M', 'G','T','P')
  1124. class Progress(object):
  1125. unknown = 'UNKNOWN'
  1126. def __init__(self, minval=0, maxval=100):
  1127. assert maxval is None or maxval >= minval
  1128. self.min = self.cur = minval
  1129. self.max = maxval
  1130. self.started = None
  1131. self.elapsed = 0
  1132. self.done = False
  1133. def update(self, curval):
  1134. assert self.min <= curval
  1135. assert self.max is None or curval <= self.max
  1136. self.cur = curval
  1137. now = time.time()
  1138. if self.started is None:
  1139. self.started = now
  1140. else:
  1141. self.elapsed = now - self.started
  1142. def increment(self, incr):
  1143. assert incr >= 0
  1144. self.update(self.cur + incr)
  1145. def start(self):
  1146. self.update(self.min)
  1147. return self
  1148. def stop(self):
  1149. if self.max is not None:
  1150. self.update(self.max)
  1151. self.done = True
  1152. @property
  1153. def maximum(self):
  1154. return self.unknown if self.max is None else self.max
  1155. @property
  1156. def percentage(self):
  1157. if self.done:
  1158. result = '100 %'
  1159. elif self.max is None:
  1160. result = ' ?? %'
  1161. else:
  1162. v = 100.0 * (self.cur - self.min) / (self.max - self.min)
  1163. result = '%3d %%' % v
  1164. return result
  1165. def format_duration(self, duration):
  1166. if (duration <= 0) and self.max is None or self.cur == self.min:
  1167. result = '??:??:??'
  1168. #elif duration < 1:
  1169. # result = '--:--:--'
  1170. else:
  1171. result = time.strftime('%H:%M:%S', time.gmtime(duration))
  1172. return result
  1173. @property
  1174. def ETA(self):
  1175. if self.done:
  1176. prefix = 'Done'
  1177. t = self.elapsed
  1178. #import pdb; pdb.set_trace()
  1179. else:
  1180. prefix = 'ETA '
  1181. if self.max is None:
  1182. t = -1
  1183. elif self.elapsed == 0 or (self.cur == self.min):
  1184. t = 0
  1185. else:
  1186. #import pdb; pdb.set_trace()
  1187. t = float(self.max - self.min)
  1188. t /= self.cur - self.min
  1189. t = (t - 1) * self.elapsed
  1190. return '%s: %s' % (prefix, self.format_duration(t))
  1191. @property
  1192. def speed(self):
  1193. if self.elapsed == 0:
  1194. result = 0.0
  1195. else:
  1196. result = (self.cur - self.min) / self.elapsed
  1197. for unit in UNITS:
  1198. if result < 1000:
  1199. break
  1200. result /= 1000.0
  1201. return '%d %sB/s' % (result, unit)
  1202. #
  1203. # Glob functionality
  1204. #
  1205. RICH_GLOB = re.compile(r'\{([^}]*)\}')
  1206. _CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
  1207. _CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
  1208. def iglob(path_glob):
  1209. """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
  1210. if _CHECK_RECURSIVE_GLOB.search(path_glob):
  1211. msg = """invalid glob %r: recursive glob "**" must be used alone"""
  1212. raise ValueError(msg % path_glob)
  1213. if _CHECK_MISMATCH_SET.search(path_glob):
  1214. msg = """invalid glob %r: mismatching set marker '{' or '}'"""
  1215. raise ValueError(msg % path_glob)
  1216. return _iglob(path_glob)
  1217. def _iglob(path_glob):
  1218. rich_path_glob = RICH_GLOB.split(path_glob, 1)
  1219. if len(rich_path_glob) > 1:
  1220. assert len(rich_path_glob) == 3, rich_path_glob
  1221. prefix, set, suffix = rich_path_glob
  1222. for item in set.split(','):
  1223. for path in _iglob(''.join((prefix, item, suffix))):
  1224. yield path
  1225. else:
  1226. if '**' not in path_glob:
  1227. for item in std_iglob(path_glob):
  1228. yield item
  1229. else:
  1230. prefix, radical = path_glob.split('**', 1)
  1231. if prefix == '':
  1232. prefix = '.'
  1233. if radical == '':
  1234. radical = '*'
  1235. else:
  1236. # we support both
  1237. radical = radical.lstrip('/')
  1238. radical = radical.lstrip('\\')
  1239. for path, dir, files in os.walk(prefix):
  1240. path = os.path.normpath(path)
  1241. for fn in _iglob(os.path.join(path, radical)):
  1242. yield fn
  1243. if ssl:
  1244. from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname,
  1245. CertificateError)
  1246. #
  1247. # HTTPSConnection which verifies certificates/matches domains
  1248. #
  1249. class HTTPSConnection(httplib.HTTPSConnection):
  1250. ca_certs = None # set this to the path to the certs file (.pem)
  1251. check_domain = True # only used if ca_certs is not None
  1252. # noinspection PyPropertyAccess
  1253. def connect(self):
  1254. sock = socket.create_connection((self.host, self.port), self.timeout)
  1255. if getattr(self, '_tunnel_host', False):
  1256. self.sock = sock
  1257. self._tunnel()
  1258. if not hasattr(ssl, 'SSLContext'):
  1259. # For 2.x
  1260. if self.ca_certs:
  1261. cert_reqs = ssl.CERT_REQUIRED
  1262. else:
  1263. cert_reqs = ssl.CERT_NONE
  1264. self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
  1265. cert_reqs=cert_reqs,
  1266. ssl_version=ssl.PROTOCOL_SSLv23,
  1267. ca_certs=self.ca_certs)
  1268. else: # pragma: no cover
  1269. context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  1270. if hasattr(ssl, 'OP_NO_SSLv2'):
  1271. context.options |= ssl.OP_NO_SSLv2
  1272. if self.cert_file:
  1273. context.load_cert_chain(self.cert_file, self.key_file)
  1274. kwargs = {}
  1275. if self.ca_certs:
  1276. context.verify_mode = ssl.CERT_REQUIRED
  1277. context.load_verify_locations(cafile=self.ca_certs)
  1278. if getattr(ssl, 'HAS_SNI', False):
  1279. kwargs['server_hostname'] = self.host
  1280. self.sock = context.wrap_socket(sock, **kwargs)
  1281. if self.ca_certs and self.check_domain:
  1282. try:
  1283. match_hostname(self.sock.getpeercert(), self.host)
  1284. logger.debug('Host verified: %s', self.host)
  1285. except CertificateError: # pragma: no cover
  1286. self.sock.shutdown(socket.SHUT_RDWR)
  1287. self.sock.close()
  1288. raise
  1289. class HTTPSHandler(BaseHTTPSHandler):
  1290. def __init__(self, ca_certs, check_domain=True):
  1291. BaseHTTPSHandler.__init__(self)
  1292. self.ca_certs = ca_certs
  1293. self.check_domain = check_domain
  1294. def _conn_maker(self, *args, **kwargs):
  1295. """
  1296. This is called to create a connection instance. Normally you'd
  1297. pass a connection class to do_open, but it doesn't actually check for
  1298. a class, and just expects a callable. As long as we behave just as a
  1299. constructor would have, we should be OK. If it ever changes so that
  1300. we *must* pass a class, we'll create an UnsafeHTTPSConnection class
  1301. which just sets check_domain to False in the class definition, and
  1302. choose which one to pass to do_open.
  1303. """
  1304. result = HTTPSConnection(*args, **kwargs)
  1305. if self.ca_certs:
  1306. result.ca_certs = self.ca_certs
  1307. result.check_domain = self.check_domain
  1308. return result
  1309. def https_open(self, req):
  1310. try:
  1311. return self.do_open(self._conn_maker, req)
  1312. except URLError as e:
  1313. if 'certificate verify failed' in str(e.reason):
  1314. raise CertificateError('Unable to verify server certificate '
  1315. 'for %s' % req.host)
  1316. else:
  1317. raise
  1318. #
  1319. # To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The-
  1320. # Middle proxy using HTTP listens on port 443, or an index mistakenly serves
  1321. # HTML containing a http://xyz link when it should be https://xyz),
  1322. # you can use the following handler class, which does not allow HTTP traffic.
  1323. #
  1324. # It works by inheriting from HTTPHandler - so build_opener won't add a
  1325. # handler for HTTP itself.
  1326. #
  1327. class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler):
  1328. def http_open(self, req):
  1329. raise URLError('Unexpected HTTP request on what should be a secure '
  1330. 'connection: %s' % req)
  1331. #
  1332. # XML-RPC with timeouts
  1333. #
  1334. _ver_info = sys.version_info[:2]
  1335. if _ver_info == (2, 6):
  1336. class HTTP(httplib.HTTP):
  1337. def __init__(self, host='', port=None, **kwargs):
  1338. if port == 0: # 0 means use port 0, not the default port
  1339. port = None
  1340. self._setup(self._connection_class(host, port, **kwargs))
  1341. if ssl:
  1342. class HTTPS(httplib.HTTPS):
  1343. def __init__(self, host='', port=None, **kwargs):
  1344. if port == 0: # 0 means use port 0, not the default port
  1345. port = None
  1346. self._setup(self._connection_class(host, port, **kwargs))
  1347. class Transport(xmlrpclib.Transport):
  1348. def __init__(self, timeout, use_datetime=0):
  1349. self.timeout = timeout
  1350. xmlrpclib.Transport.__init__(self, use_datetime)
  1351. def make_connection(self, host):
  1352. h, eh, x509 = self.get_host_info(host)
  1353. if _ver_info == (2, 6):
  1354. result = HTTP(h, timeout=self.timeout)
  1355. else:
  1356. if not self._connection or host != self._connection[0]:
  1357. self._extra_headers = eh
  1358. self._connection = host, httplib.HTTPConnection(h)
  1359. result = self._connection[1]
  1360. return result
  1361. if ssl:
  1362. class SafeTransport(xmlrpclib.SafeTransport):
  1363. def __init__(self, timeout, use_datetime=0):
  1364. self.timeout = timeout
  1365. xmlrpclib.SafeTransport.__init__(self, use_datetime)
  1366. def make_connection(self, host):
  1367. h, eh, kwargs = self.get_host_info(host)
  1368. if not kwargs:
  1369. kwargs = {}
  1370. kwargs['timeout'] = self.timeout
  1371. if _ver_info == (2, 6):
  1372. result = HTTPS(host, None, **kwargs)
  1373. else:
  1374. if not self._connection or host != self._connection[0]:
  1375. self._extra_headers = eh
  1376. self._connection = host, httplib.HTTPSConnection(h, None,
  1377. **kwargs)
  1378. result = self._connection[1]
  1379. return result
  1380. class ServerProxy(xmlrpclib.ServerProxy):
  1381. def __init__(self, uri, **kwargs):
  1382. self.timeout = timeout = kwargs.pop('timeout', None)
  1383. # The above classes only come into play if a timeout
  1384. # is specified
  1385. if timeout is not None:
  1386. # scheme = splittype(uri) # deprecated as of Python 3.8
  1387. scheme = urlparse(uri)[0]
  1388. use_datetime = kwargs.get('use_datetime', 0)
  1389. if scheme == 'https':
  1390. tcls = SafeTransport
  1391. else:
  1392. tcls = Transport
  1393. kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime)
  1394. self.transport = t
  1395. xmlrpclib.ServerProxy.__init__(self, uri, **kwargs)
  1396. #
  1397. # CSV functionality. This is provided because on 2.x, the csv module can't
  1398. # handle Unicode. However, we need to deal with Unicode in e.g. RECORD files.
  1399. #
  1400. def _csv_open(fn, mode, **kwargs):
  1401. if sys.version_info[0] < 3:
  1402. mode += 'b'
  1403. else:
  1404. kwargs['newline'] = ''
  1405. # Python 3 determines encoding from locale. Force 'utf-8'
  1406. # file encoding to match other forced utf-8 encoding
  1407. kwargs['encoding'] = 'utf-8'
  1408. return open(fn, mode, **kwargs)
  1409. class CSVBase(object):
  1410. defaults = {
  1411. 'delimiter': str(','), # The strs are used because we need native
  1412. 'quotechar': str('"'), # str in the csv API (2.x won't take
  1413. 'lineterminator': str('\n') # Unicode)
  1414. }
  1415. def __enter__(self):
  1416. return self
  1417. def __exit__(self, *exc_info):
  1418. self.stream.close()
  1419. class CSVReader(CSVBase):
  1420. def __init__(self, **kwargs):
  1421. if 'stream' in kwargs:
  1422. stream = kwargs['stream']
  1423. if sys.version_info[0] >= 3:
  1424. # needs to be a text stream
  1425. stream = codecs.getreader('utf-8')(stream)
  1426. self.stream = stream
  1427. else:
  1428. self.stream = _csv_open(kwargs['path'], 'r')
  1429. self.reader = csv.reader(self.stream, **self.defaults)
  1430. def __iter__(self):
  1431. return self
  1432. def next(self):
  1433. result = next(self.reader)
  1434. if sys.version_info[0] < 3:
  1435. for i, item in enumerate(result):
  1436. if not isinstance(item, text_type):
  1437. result[i] = item.decode('utf-8')
  1438. return result
  1439. __next__ = next
  1440. class CSVWriter(CSVBase):
  1441. def __init__(self, fn, **kwargs):
  1442. self.stream = _csv_open(fn, 'w')
  1443. self.writer = csv.writer(self.stream, **self.defaults)
  1444. def writerow(self, row):
  1445. if sys.version_info[0] < 3:
  1446. r = []
  1447. for item in row:
  1448. if isinstance(item, text_type):
  1449. item = item.encode('utf-8')
  1450. r.append(item)
  1451. row = r
  1452. self.writer.writerow(row)
  1453. #
  1454. # Configurator functionality
  1455. #
  1456. class Configurator(BaseConfigurator):
  1457. value_converters = dict(BaseConfigurator.value_converters)
  1458. value_converters['inc'] = 'inc_convert'
  1459. def __init__(self, config, base=None):
  1460. super(Configurator, self).__init__(config)
  1461. self.base = base or os.getcwd()
  1462. def configure_custom(self, config):
  1463. def convert(o):
  1464. if isinstance(o, (list, tuple)):
  1465. result = type(o)([convert(i) for i in o])
  1466. elif isinstance(o, dict):
  1467. if '()' in o:
  1468. result = self.configure_custom(o)
  1469. else:
  1470. result = {}
  1471. for k in o:
  1472. result[k] = convert(o[k])
  1473. else:
  1474. result = self.convert(o)
  1475. return result
  1476. c = config.pop('()')
  1477. if not callable(c):
  1478. c = self.resolve(c)
  1479. props = config.pop('.', None)
  1480. # Check for valid identifiers
  1481. args = config.pop('[]', ())
  1482. if args:
  1483. args = tuple([convert(o) for o in args])
  1484. items = [(k, convert(config[k])) for k in config if valid_ident(k)]
  1485. kwargs = dict(items)
  1486. result = c(*args, **kwargs)
  1487. if props:
  1488. for n, v in props.items():
  1489. setattr(result, n, convert(v))
  1490. return result
  1491. def __getitem__(self, key):
  1492. result = self.config[key]
  1493. if isinstance(result, dict) and '()' in result:
  1494. self.config[key] = result = self.configure_custom(result)
  1495. return result
  1496. def inc_convert(self, value):
  1497. """Default converter for the inc:// protocol."""
  1498. if not os.path.isabs(value):
  1499. value = os.path.join(self.base, value)
  1500. with codecs.open(value, 'r', encoding='utf-8') as f:
  1501. result = json.load(f)
  1502. return result
  1503. class SubprocessMixin(object):
  1504. """
  1505. Mixin for running subprocesses and capturing their output
  1506. """
  1507. def __init__(self, verbose=False, progress=None):
  1508. self.verbose = verbose
  1509. self.progress = progress
  1510. def reader(self, stream, context):
  1511. """
  1512. Read lines from a subprocess' output stream and either pass to a progress
  1513. callable (if specified) or write progress information to sys.stderr.
  1514. """
  1515. progress = self.progress
  1516. verbose = self.verbose
  1517. while True:
  1518. s = stream.readline()
  1519. if not s:
  1520. break
  1521. if progress is not None:
  1522. progress(s, context)
  1523. else:
  1524. if not verbose:
  1525. sys.stderr.write('.')
  1526. else:
  1527. sys.stderr.write(s.decode('utf-8'))
  1528. sys.stderr.flush()
  1529. stream.close()
  1530. def run_command(self, cmd, **kwargs):
  1531. p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
  1532. stderr=subprocess.PIPE, **kwargs)
  1533. t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout'))
  1534. t1.start()
  1535. t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr'))
  1536. t2.start()
  1537. p.wait()
  1538. t1.join()
  1539. t2.join()
  1540. if self.progress is not None:
  1541. self.progress('done.', 'main')
  1542. elif self.verbose:
  1543. sys.stderr.write('done.\n')
  1544. return p
  1545. def normalize_name(name):
  1546. """Normalize a python package name a la PEP 503"""
  1547. # https://www.python.org/dev/peps/pep-0503/#normalized-names
  1548. return re.sub('[-_.]+', '-', name).lower()
  1549. # def _get_pypirc_command():
  1550. # """
  1551. # Get the distutils command for interacting with PyPI configurations.
  1552. # :return: the command.
  1553. # """
  1554. # from distutils.core import Distribution
  1555. # from distutils.config import PyPIRCCommand
  1556. # d = Distribution()
  1557. # return PyPIRCCommand(d)
  1558. class PyPIRCFile(object):
  1559. DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/'
  1560. DEFAULT_REALM = 'pypi'
  1561. def __init__(self, fn=None, url=None):
  1562. if fn is None:
  1563. fn = os.path.join(os.path.expanduser('~'), '.pypirc')
  1564. self.filename = fn
  1565. self.url = url
  1566. def read(self):
  1567. result = {}
  1568. if os.path.exists(self.filename):
  1569. repository = self.url or self.DEFAULT_REPOSITORY
  1570. config = configparser.RawConfigParser()
  1571. config.read(self.filename)
  1572. sections = config.sections()
  1573. if 'distutils' in sections:
  1574. # let's get the list of servers
  1575. index_servers = config.get('distutils', 'index-servers')
  1576. _servers = [server.strip() for server in
  1577. index_servers.split('\n')
  1578. if server.strip() != '']
  1579. if _servers == []:
  1580. # nothing set, let's try to get the default pypi
  1581. if 'pypi' in sections:
  1582. _servers = ['pypi']
  1583. else:
  1584. for server in _servers:
  1585. result = {'server': server}
  1586. result['username'] = config.get(server, 'username')
  1587. # optional params
  1588. for key, default in (('repository', self.DEFAULT_REPOSITORY),
  1589. ('realm', self.DEFAULT_REALM),
  1590. ('password', None)):
  1591. if config.has_option(server, key):
  1592. result[key] = config.get(server, key)
  1593. else:
  1594. result[key] = default
  1595. # work around people having "repository" for the "pypi"
  1596. # section of their config set to the HTTP (rather than
  1597. # HTTPS) URL
  1598. if (server == 'pypi' and
  1599. repository in (self.DEFAULT_REPOSITORY, 'pypi')):
  1600. result['repository'] = self.DEFAULT_REPOSITORY
  1601. elif (result['server'] != repository and
  1602. result['repository'] != repository):
  1603. result = {}
  1604. elif 'server-login' in sections:
  1605. # old format
  1606. server = 'server-login'
  1607. if config.has_option(server, 'repository'):
  1608. repository = config.get(server, 'repository')
  1609. else:
  1610. repository = self.DEFAULT_REPOSITORY
  1611. result = {
  1612. 'username': config.get(server, 'username'),
  1613. 'password': config.get(server, 'password'),
  1614. 'repository': repository,
  1615. 'server': server,
  1616. 'realm': self.DEFAULT_REALM
  1617. }
  1618. return result
  1619. def update(self, username, password):
  1620. # import pdb; pdb.set_trace()
  1621. config = configparser.RawConfigParser()
  1622. fn = self.filename
  1623. config.read(fn)
  1624. if not config.has_section('pypi'):
  1625. config.add_section('pypi')
  1626. config.set('pypi', 'username', username)
  1627. config.set('pypi', 'password', password)
  1628. with open(fn, 'w') as f:
  1629. config.write(f)
  1630. def _load_pypirc(index):
  1631. """
  1632. Read the PyPI access configuration as supported by distutils.
  1633. """
  1634. return PyPIRCFile(url=index.url).read()
  1635. def _store_pypirc(index):
  1636. PyPIRCFile().update(index.username, index.password)
  1637. #
  1638. # get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor
  1639. # tweaks
  1640. #
  1641. def get_host_platform():
  1642. """Return a string that identifies the current platform. This is used mainly to
  1643. distinguish platform-specific build directories and platform-specific built
  1644. distributions. Typically includes the OS name and version and the
  1645. architecture (as supplied by 'os.uname()'), although the exact information
  1646. included depends on the OS; eg. on Linux, the kernel version isn't
  1647. particularly important.
  1648. Examples of returned values:
  1649. linux-i586
  1650. linux-alpha (?)
  1651. solaris-2.6-sun4u
  1652. Windows will return one of:
  1653. win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
  1654. win32 (all others - specifically, sys.platform is returned)
  1655. For other non-POSIX platforms, currently just returns 'sys.platform'.
  1656. """
  1657. if os.name == 'nt':
  1658. if 'amd64' in sys.version.lower():
  1659. return 'win-amd64'
  1660. if '(arm)' in sys.version.lower():
  1661. return 'win-arm32'
  1662. if '(arm64)' in sys.version.lower():
  1663. return 'win-arm64'
  1664. return sys.platform
  1665. # Set for cross builds explicitly
  1666. if "_PYTHON_HOST_PLATFORM" in os.environ:
  1667. return os.environ["_PYTHON_HOST_PLATFORM"]
  1668. if os.name != 'posix' or not hasattr(os, 'uname'):
  1669. # XXX what about the architecture? NT is Intel or Alpha,
  1670. # Mac OS is M68k or PPC, etc.
  1671. return sys.platform
  1672. # Try to distinguish various flavours of Unix
  1673. (osname, host, release, version, machine) = os.uname()
  1674. # Convert the OS name to lowercase, remove '/' characters, and translate
  1675. # spaces (for "Power Macintosh")
  1676. osname = osname.lower().replace('/', '')
  1677. machine = machine.replace(' ', '_').replace('/', '-')
  1678. if osname[:5] == 'linux':
  1679. # At least on Linux/Intel, 'machine' is the processor --
  1680. # i386, etc.
  1681. # XXX what about Alpha, SPARC, etc?
  1682. return "%s-%s" % (osname, machine)
  1683. elif osname[:5] == 'sunos':
  1684. if release[0] >= '5': # SunOS 5 == Solaris 2
  1685. osname = 'solaris'
  1686. release = '%d.%s' % (int(release[0]) - 3, release[2:])
  1687. # We can't use 'platform.architecture()[0]' because a
  1688. # bootstrap problem. We use a dict to get an error
  1689. # if some suspicious happens.
  1690. bitness = {2147483647:'32bit', 9223372036854775807:'64bit'}
  1691. machine += '.%s' % bitness[sys.maxsize]
  1692. # fall through to standard osname-release-machine representation
  1693. elif osname[:3] == 'aix':
  1694. from _aix_support import aix_platform
  1695. return aix_platform()
  1696. elif osname[:6] == 'cygwin':
  1697. osname = 'cygwin'
  1698. rel_re = re.compile (r'[\d.]+', re.ASCII)
  1699. m = rel_re.match(release)
  1700. if m:
  1701. release = m.group()
  1702. elif osname[:6] == 'darwin':
  1703. import _osx_support, distutils.sysconfig
  1704. osname, release, machine = _osx_support.get_platform_osx(
  1705. distutils.sysconfig.get_config_vars(),
  1706. osname, release, machine)
  1707. return '%s-%s-%s' % (osname, release, machine)
  1708. _TARGET_TO_PLAT = {
  1709. 'x86' : 'win32',
  1710. 'x64' : 'win-amd64',
  1711. 'arm' : 'win-arm32',
  1712. }
  1713. def get_platform():
  1714. if os.name != 'nt':
  1715. return get_host_platform()
  1716. cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH')
  1717. if cross_compilation_target not in _TARGET_TO_PLAT:
  1718. return get_host_platform()
  1719. return _TARGET_TO_PLAT[cross_compilation_target]