local.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. """
  2. local path implementation.
  3. """
  4. from __future__ import with_statement
  5. from contextlib import contextmanager
  6. import sys, os, re, atexit, io, uuid
  7. import py
  8. from py._path import common
  9. from py._path.common import iswin32, fspath
  10. from stat import S_ISLNK, S_ISDIR, S_ISREG
  11. from os.path import abspath, normcase, normpath, isabs, exists, isdir, isfile, islink, dirname
  12. if sys.version_info > (3,0):
  13. def map_as_list(func, iter):
  14. return list(map(func, iter))
  15. else:
  16. map_as_list = map
  17. class Stat(object):
  18. def __getattr__(self, name):
  19. return getattr(self._osstatresult, "st_" + name)
  20. def __init__(self, path, osstatresult):
  21. self.path = path
  22. self._osstatresult = osstatresult
  23. @property
  24. def owner(self):
  25. if iswin32:
  26. raise NotImplementedError("XXX win32")
  27. import pwd
  28. entry = py.error.checked_call(pwd.getpwuid, self.uid)
  29. return entry[0]
  30. @property
  31. def group(self):
  32. """ return group name of file. """
  33. if iswin32:
  34. raise NotImplementedError("XXX win32")
  35. import grp
  36. entry = py.error.checked_call(grp.getgrgid, self.gid)
  37. return entry[0]
  38. def isdir(self):
  39. return S_ISDIR(self._osstatresult.st_mode)
  40. def isfile(self):
  41. return S_ISREG(self._osstatresult.st_mode)
  42. def islink(self):
  43. st = self.path.lstat()
  44. return S_ISLNK(self._osstatresult.st_mode)
  45. class PosixPath(common.PathBase):
  46. def chown(self, user, group, rec=0):
  47. """ change ownership to the given user and group.
  48. user and group may be specified by a number or
  49. by a name. if rec is True change ownership
  50. recursively.
  51. """
  52. uid = getuserid(user)
  53. gid = getgroupid(group)
  54. if rec:
  55. for x in self.visit(rec=lambda x: x.check(link=0)):
  56. if x.check(link=0):
  57. py.error.checked_call(os.chown, str(x), uid, gid)
  58. py.error.checked_call(os.chown, str(self), uid, gid)
  59. def readlink(self):
  60. """ return value of a symbolic link. """
  61. return py.error.checked_call(os.readlink, self.strpath)
  62. def mklinkto(self, oldname):
  63. """ posix style hard link to another name. """
  64. py.error.checked_call(os.link, str(oldname), str(self))
  65. def mksymlinkto(self, value, absolute=1):
  66. """ create a symbolic link with the given value (pointing to another name). """
  67. if absolute:
  68. py.error.checked_call(os.symlink, str(value), self.strpath)
  69. else:
  70. base = self.common(value)
  71. # with posix local paths '/' is always a common base
  72. relsource = self.__class__(value).relto(base)
  73. reldest = self.relto(base)
  74. n = reldest.count(self.sep)
  75. target = self.sep.join(('..', )*n + (relsource, ))
  76. py.error.checked_call(os.symlink, target, self.strpath)
  77. def getuserid(user):
  78. import pwd
  79. if not isinstance(user, int):
  80. user = pwd.getpwnam(user)[2]
  81. return user
  82. def getgroupid(group):
  83. import grp
  84. if not isinstance(group, int):
  85. group = grp.getgrnam(group)[2]
  86. return group
  87. FSBase = not iswin32 and PosixPath or common.PathBase
  88. class LocalPath(FSBase):
  89. """ object oriented interface to os.path and other local filesystem
  90. related information.
  91. """
  92. class ImportMismatchError(ImportError):
  93. """ raised on pyimport() if there is a mismatch of __file__'s"""
  94. sep = os.sep
  95. class Checkers(common.Checkers):
  96. def _stat(self):
  97. try:
  98. return self._statcache
  99. except AttributeError:
  100. try:
  101. self._statcache = self.path.stat()
  102. except py.error.ELOOP:
  103. self._statcache = self.path.lstat()
  104. return self._statcache
  105. def dir(self):
  106. return S_ISDIR(self._stat().mode)
  107. def file(self):
  108. return S_ISREG(self._stat().mode)
  109. def exists(self):
  110. return self._stat()
  111. def link(self):
  112. st = self.path.lstat()
  113. return S_ISLNK(st.mode)
  114. def __init__(self, path=None, expanduser=False):
  115. """ Initialize and return a local Path instance.
  116. Path can be relative to the current directory.
  117. If path is None it defaults to the current working directory.
  118. If expanduser is True, tilde-expansion is performed.
  119. Note that Path instances always carry an absolute path.
  120. Note also that passing in a local path object will simply return
  121. the exact same path object. Use new() to get a new copy.
  122. """
  123. if path is None:
  124. self.strpath = py.error.checked_call(os.getcwd)
  125. else:
  126. try:
  127. path = fspath(path)
  128. except TypeError:
  129. raise ValueError("can only pass None, Path instances "
  130. "or non-empty strings to LocalPath")
  131. if expanduser:
  132. path = os.path.expanduser(path)
  133. self.strpath = abspath(path)
  134. def __hash__(self):
  135. return hash(self.strpath)
  136. def __eq__(self, other):
  137. s1 = fspath(self)
  138. try:
  139. s2 = fspath(other)
  140. except TypeError:
  141. return False
  142. if iswin32:
  143. s1 = s1.lower()
  144. try:
  145. s2 = s2.lower()
  146. except AttributeError:
  147. return False
  148. return s1 == s2
  149. def __ne__(self, other):
  150. return not (self == other)
  151. def __lt__(self, other):
  152. return fspath(self) < fspath(other)
  153. def __gt__(self, other):
  154. return fspath(self) > fspath(other)
  155. def samefile(self, other):
  156. """ return True if 'other' references the same file as 'self'.
  157. """
  158. other = fspath(other)
  159. if not isabs(other):
  160. other = abspath(other)
  161. if self == other:
  162. return True
  163. if iswin32:
  164. return False # there is no samefile
  165. return py.error.checked_call(
  166. os.path.samefile, self.strpath, other)
  167. def remove(self, rec=1, ignore_errors=False):
  168. """ remove a file or directory (or a directory tree if rec=1).
  169. if ignore_errors is True, errors while removing directories will
  170. be ignored.
  171. """
  172. if self.check(dir=1, link=0):
  173. if rec:
  174. # force remove of readonly files on windows
  175. if iswin32:
  176. self.chmod(0o700, rec=1)
  177. import shutil
  178. py.error.checked_call(
  179. shutil.rmtree, self.strpath,
  180. ignore_errors=ignore_errors)
  181. else:
  182. py.error.checked_call(os.rmdir, self.strpath)
  183. else:
  184. if iswin32:
  185. self.chmod(0o700)
  186. py.error.checked_call(os.remove, self.strpath)
  187. def computehash(self, hashtype="md5", chunksize=524288):
  188. """ return hexdigest of hashvalue for this file. """
  189. try:
  190. try:
  191. import hashlib as mod
  192. except ImportError:
  193. if hashtype == "sha1":
  194. hashtype = "sha"
  195. mod = __import__(hashtype)
  196. hash = getattr(mod, hashtype)()
  197. except (AttributeError, ImportError):
  198. raise ValueError("Don't know how to compute %r hash" %(hashtype,))
  199. f = self.open('rb')
  200. try:
  201. while 1:
  202. buf = f.read(chunksize)
  203. if not buf:
  204. return hash.hexdigest()
  205. hash.update(buf)
  206. finally:
  207. f.close()
  208. def new(self, **kw):
  209. """ create a modified version of this path.
  210. the following keyword arguments modify various path parts::
  211. a:/some/path/to/a/file.ext
  212. xx drive
  213. xxxxxxxxxxxxxxxxx dirname
  214. xxxxxxxx basename
  215. xxxx purebasename
  216. xxx ext
  217. """
  218. obj = object.__new__(self.__class__)
  219. if not kw:
  220. obj.strpath = self.strpath
  221. return obj
  222. drive, dirname, basename, purebasename,ext = self._getbyspec(
  223. "drive,dirname,basename,purebasename,ext")
  224. if 'basename' in kw:
  225. if 'purebasename' in kw or 'ext' in kw:
  226. raise ValueError("invalid specification %r" % kw)
  227. else:
  228. pb = kw.setdefault('purebasename', purebasename)
  229. try:
  230. ext = kw['ext']
  231. except KeyError:
  232. pass
  233. else:
  234. if ext and not ext.startswith('.'):
  235. ext = '.' + ext
  236. kw['basename'] = pb + ext
  237. if ('dirname' in kw and not kw['dirname']):
  238. kw['dirname'] = drive
  239. else:
  240. kw.setdefault('dirname', dirname)
  241. kw.setdefault('sep', self.sep)
  242. obj.strpath = normpath(
  243. "%(dirname)s%(sep)s%(basename)s" % kw)
  244. return obj
  245. def _getbyspec(self, spec):
  246. """ see new for what 'spec' can be. """
  247. res = []
  248. parts = self.strpath.split(self.sep)
  249. args = filter(None, spec.split(',') )
  250. append = res.append
  251. for name in args:
  252. if name == 'drive':
  253. append(parts[0])
  254. elif name == 'dirname':
  255. append(self.sep.join(parts[:-1]))
  256. else:
  257. basename = parts[-1]
  258. if name == 'basename':
  259. append(basename)
  260. else:
  261. i = basename.rfind('.')
  262. if i == -1:
  263. purebasename, ext = basename, ''
  264. else:
  265. purebasename, ext = basename[:i], basename[i:]
  266. if name == 'purebasename':
  267. append(purebasename)
  268. elif name == 'ext':
  269. append(ext)
  270. else:
  271. raise ValueError("invalid part specification %r" % name)
  272. return res
  273. def dirpath(self, *args, **kwargs):
  274. """ return the directory path joined with any given path arguments. """
  275. if not kwargs:
  276. path = object.__new__(self.__class__)
  277. path.strpath = dirname(self.strpath)
  278. if args:
  279. path = path.join(*args)
  280. return path
  281. return super(LocalPath, self).dirpath(*args, **kwargs)
  282. def join(self, *args, **kwargs):
  283. """ return a new path by appending all 'args' as path
  284. components. if abs=1 is used restart from root if any
  285. of the args is an absolute path.
  286. """
  287. sep = self.sep
  288. strargs = [fspath(arg) for arg in args]
  289. strpath = self.strpath
  290. if kwargs.get('abs'):
  291. newargs = []
  292. for arg in reversed(strargs):
  293. if isabs(arg):
  294. strpath = arg
  295. strargs = newargs
  296. break
  297. newargs.insert(0, arg)
  298. # special case for when we have e.g. strpath == "/"
  299. actual_sep = "" if strpath.endswith(sep) else sep
  300. for arg in strargs:
  301. arg = arg.strip(sep)
  302. if iswin32:
  303. # allow unix style paths even on windows.
  304. arg = arg.strip('/')
  305. arg = arg.replace('/', sep)
  306. strpath = strpath + actual_sep + arg
  307. actual_sep = sep
  308. obj = object.__new__(self.__class__)
  309. obj.strpath = normpath(strpath)
  310. return obj
  311. def open(self, mode='r', ensure=False, encoding=None):
  312. """ return an opened file with the given mode.
  313. If ensure is True, create parent directories if needed.
  314. """
  315. if ensure:
  316. self.dirpath().ensure(dir=1)
  317. if encoding:
  318. return py.error.checked_call(io.open, self.strpath, mode, encoding=encoding)
  319. return py.error.checked_call(open, self.strpath, mode)
  320. def _fastjoin(self, name):
  321. child = object.__new__(self.__class__)
  322. child.strpath = self.strpath + self.sep + name
  323. return child
  324. def islink(self):
  325. return islink(self.strpath)
  326. def check(self, **kw):
  327. if not kw:
  328. return exists(self.strpath)
  329. if len(kw) == 1:
  330. if "dir" in kw:
  331. return not kw["dir"] ^ isdir(self.strpath)
  332. if "file" in kw:
  333. return not kw["file"] ^ isfile(self.strpath)
  334. return super(LocalPath, self).check(**kw)
  335. _patternchars = set("*?[" + os.path.sep)
  336. def listdir(self, fil=None, sort=None):
  337. """ list directory contents, possibly filter by the given fil func
  338. and possibly sorted.
  339. """
  340. if fil is None and sort is None:
  341. names = py.error.checked_call(os.listdir, self.strpath)
  342. return map_as_list(self._fastjoin, names)
  343. if isinstance(fil, py.builtin._basestring):
  344. if not self._patternchars.intersection(fil):
  345. child = self._fastjoin(fil)
  346. if exists(child.strpath):
  347. return [child]
  348. return []
  349. fil = common.FNMatcher(fil)
  350. names = py.error.checked_call(os.listdir, self.strpath)
  351. res = []
  352. for name in names:
  353. child = self._fastjoin(name)
  354. if fil is None or fil(child):
  355. res.append(child)
  356. self._sortlist(res, sort)
  357. return res
  358. def size(self):
  359. """ return size of the underlying file object """
  360. return self.stat().size
  361. def mtime(self):
  362. """ return last modification time of the path. """
  363. return self.stat().mtime
  364. def copy(self, target, mode=False, stat=False):
  365. """ copy path to target.
  366. If mode is True, will copy copy permission from path to target.
  367. If stat is True, copy permission, last modification
  368. time, last access time, and flags from path to target.
  369. """
  370. if self.check(file=1):
  371. if target.check(dir=1):
  372. target = target.join(self.basename)
  373. assert self!=target
  374. copychunked(self, target)
  375. if mode:
  376. copymode(self.strpath, target.strpath)
  377. if stat:
  378. copystat(self, target)
  379. else:
  380. def rec(p):
  381. return p.check(link=0)
  382. for x in self.visit(rec=rec):
  383. relpath = x.relto(self)
  384. newx = target.join(relpath)
  385. newx.dirpath().ensure(dir=1)
  386. if x.check(link=1):
  387. newx.mksymlinkto(x.readlink())
  388. continue
  389. elif x.check(file=1):
  390. copychunked(x, newx)
  391. elif x.check(dir=1):
  392. newx.ensure(dir=1)
  393. if mode:
  394. copymode(x.strpath, newx.strpath)
  395. if stat:
  396. copystat(x, newx)
  397. def rename(self, target):
  398. """ rename this path to target. """
  399. target = fspath(target)
  400. return py.error.checked_call(os.rename, self.strpath, target)
  401. def dump(self, obj, bin=1):
  402. """ pickle object into path location"""
  403. f = self.open('wb')
  404. import pickle
  405. try:
  406. py.error.checked_call(pickle.dump, obj, f, bin)
  407. finally:
  408. f.close()
  409. def mkdir(self, *args):
  410. """ create & return the directory joined with args. """
  411. p = self.join(*args)
  412. py.error.checked_call(os.mkdir, fspath(p))
  413. return p
  414. def write_binary(self, data, ensure=False):
  415. """ write binary data into path. If ensure is True create
  416. missing parent directories.
  417. """
  418. if ensure:
  419. self.dirpath().ensure(dir=1)
  420. with self.open('wb') as f:
  421. f.write(data)
  422. def write_text(self, data, encoding, ensure=False):
  423. """ write text data into path using the specified encoding.
  424. If ensure is True create missing parent directories.
  425. """
  426. if ensure:
  427. self.dirpath().ensure(dir=1)
  428. with self.open('w', encoding=encoding) as f:
  429. f.write(data)
  430. def write(self, data, mode='w', ensure=False):
  431. """ write data into path. If ensure is True create
  432. missing parent directories.
  433. """
  434. if ensure:
  435. self.dirpath().ensure(dir=1)
  436. if 'b' in mode:
  437. if not py.builtin._isbytes(data):
  438. raise ValueError("can only process bytes")
  439. else:
  440. if not py.builtin._istext(data):
  441. if not py.builtin._isbytes(data):
  442. data = str(data)
  443. else:
  444. data = py.builtin._totext(data, sys.getdefaultencoding())
  445. f = self.open(mode)
  446. try:
  447. f.write(data)
  448. finally:
  449. f.close()
  450. def _ensuredirs(self):
  451. parent = self.dirpath()
  452. if parent == self:
  453. return self
  454. if parent.check(dir=0):
  455. parent._ensuredirs()
  456. if self.check(dir=0):
  457. try:
  458. self.mkdir()
  459. except py.error.EEXIST:
  460. # race condition: file/dir created by another thread/process.
  461. # complain if it is not a dir
  462. if self.check(dir=0):
  463. raise
  464. return self
  465. def ensure(self, *args, **kwargs):
  466. """ ensure that an args-joined path exists (by default as
  467. a file). if you specify a keyword argument 'dir=True'
  468. then the path is forced to be a directory path.
  469. """
  470. p = self.join(*args)
  471. if kwargs.get('dir', 0):
  472. return p._ensuredirs()
  473. else:
  474. p.dirpath()._ensuredirs()
  475. if not p.check(file=1):
  476. p.open('w').close()
  477. return p
  478. def stat(self, raising=True):
  479. """ Return an os.stat() tuple. """
  480. if raising == True:
  481. return Stat(self, py.error.checked_call(os.stat, self.strpath))
  482. try:
  483. return Stat(self, os.stat(self.strpath))
  484. except KeyboardInterrupt:
  485. raise
  486. except Exception:
  487. return None
  488. def lstat(self):
  489. """ Return an os.lstat() tuple. """
  490. return Stat(self, py.error.checked_call(os.lstat, self.strpath))
  491. def setmtime(self, mtime=None):
  492. """ set modification time for the given path. if 'mtime' is None
  493. (the default) then the file's mtime is set to current time.
  494. Note that the resolution for 'mtime' is platform dependent.
  495. """
  496. if mtime is None:
  497. return py.error.checked_call(os.utime, self.strpath, mtime)
  498. try:
  499. return py.error.checked_call(os.utime, self.strpath, (-1, mtime))
  500. except py.error.EINVAL:
  501. return py.error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
  502. def chdir(self):
  503. """ change directory to self and return old current directory """
  504. try:
  505. old = self.__class__()
  506. except py.error.ENOENT:
  507. old = None
  508. py.error.checked_call(os.chdir, self.strpath)
  509. return old
  510. @contextmanager
  511. def as_cwd(self):
  512. """ return context manager which changes to current dir during the
  513. managed "with" context. On __enter__ it returns the old dir.
  514. """
  515. old = self.chdir()
  516. try:
  517. yield old
  518. finally:
  519. old.chdir()
  520. def realpath(self):
  521. """ return a new path which contains no symbolic links."""
  522. return self.__class__(os.path.realpath(self.strpath))
  523. def atime(self):
  524. """ return last access time of the path. """
  525. return self.stat().atime
  526. def __repr__(self):
  527. return 'local(%r)' % self.strpath
  528. def __str__(self):
  529. """ return string representation of the Path. """
  530. return self.strpath
  531. def chmod(self, mode, rec=0):
  532. """ change permissions to the given mode. If mode is an
  533. integer it directly encodes the os-specific modes.
  534. if rec is True perform recursively.
  535. """
  536. if not isinstance(mode, int):
  537. raise TypeError("mode %r must be an integer" % (mode,))
  538. if rec:
  539. for x in self.visit(rec=rec):
  540. py.error.checked_call(os.chmod, str(x), mode)
  541. py.error.checked_call(os.chmod, self.strpath, mode)
  542. def pypkgpath(self):
  543. """ return the Python package path by looking for the last
  544. directory upwards which still contains an __init__.py.
  545. Return None if a pkgpath can not be determined.
  546. """
  547. pkgpath = None
  548. for parent in self.parts(reverse=True):
  549. if parent.isdir():
  550. if not parent.join('__init__.py').exists():
  551. break
  552. if not isimportable(parent.basename):
  553. break
  554. pkgpath = parent
  555. return pkgpath
  556. def _ensuresyspath(self, ensuremode, path):
  557. if ensuremode:
  558. s = str(path)
  559. if ensuremode == "append":
  560. if s not in sys.path:
  561. sys.path.append(s)
  562. else:
  563. if s != sys.path[0]:
  564. sys.path.insert(0, s)
  565. def pyimport(self, modname=None, ensuresyspath=True):
  566. """ return path as an imported python module.
  567. If modname is None, look for the containing package
  568. and construct an according module name.
  569. The module will be put/looked up in sys.modules.
  570. if ensuresyspath is True then the root dir for importing
  571. the file (taking __init__.py files into account) will
  572. be prepended to sys.path if it isn't there already.
  573. If ensuresyspath=="append" the root dir will be appended
  574. if it isn't already contained in sys.path.
  575. if ensuresyspath is False no modification of syspath happens.
  576. """
  577. if not self.check():
  578. raise py.error.ENOENT(self)
  579. pkgpath = None
  580. if modname is None:
  581. pkgpath = self.pypkgpath()
  582. if pkgpath is not None:
  583. pkgroot = pkgpath.dirpath()
  584. names = self.new(ext="").relto(pkgroot).split(self.sep)
  585. if names[-1] == "__init__":
  586. names.pop()
  587. modname = ".".join(names)
  588. else:
  589. pkgroot = self.dirpath()
  590. modname = self.purebasename
  591. self._ensuresyspath(ensuresyspath, pkgroot)
  592. __import__(modname)
  593. mod = sys.modules[modname]
  594. if self.basename == "__init__.py":
  595. return mod # we don't check anything as we might
  596. # be in a namespace package ... too icky to check
  597. modfile = mod.__file__
  598. if modfile[-4:] in ('.pyc', '.pyo'):
  599. modfile = modfile[:-1]
  600. elif modfile.endswith('$py.class'):
  601. modfile = modfile[:-9] + '.py'
  602. if modfile.endswith(os.path.sep + "__init__.py"):
  603. if self.basename != "__init__.py":
  604. modfile = modfile[:-12]
  605. try:
  606. issame = self.samefile(modfile)
  607. except py.error.ENOENT:
  608. issame = False
  609. if not issame:
  610. raise self.ImportMismatchError(modname, modfile, self)
  611. return mod
  612. else:
  613. try:
  614. return sys.modules[modname]
  615. except KeyError:
  616. # we have a custom modname, do a pseudo-import
  617. import types
  618. mod = types.ModuleType(modname)
  619. mod.__file__ = str(self)
  620. sys.modules[modname] = mod
  621. try:
  622. py.builtin.execfile(str(self), mod.__dict__)
  623. except:
  624. del sys.modules[modname]
  625. raise
  626. return mod
  627. def sysexec(self, *argv, **popen_opts):
  628. """ return stdout text from executing a system child process,
  629. where the 'self' path points to executable.
  630. The process is directly invoked and not through a system shell.
  631. """
  632. from subprocess import Popen, PIPE
  633. argv = map_as_list(str, argv)
  634. popen_opts['stdout'] = popen_opts['stderr'] = PIPE
  635. proc = Popen([str(self)] + argv, **popen_opts)
  636. stdout, stderr = proc.communicate()
  637. ret = proc.wait()
  638. if py.builtin._isbytes(stdout):
  639. stdout = py.builtin._totext(stdout, sys.getdefaultencoding())
  640. if ret != 0:
  641. if py.builtin._isbytes(stderr):
  642. stderr = py.builtin._totext(stderr, sys.getdefaultencoding())
  643. raise py.process.cmdexec.Error(ret, ret, str(self),
  644. stdout, stderr,)
  645. return stdout
  646. def sysfind(cls, name, checker=None, paths=None):
  647. """ return a path object found by looking at the systems
  648. underlying PATH specification. If the checker is not None
  649. it will be invoked to filter matching paths. If a binary
  650. cannot be found, None is returned
  651. Note: This is probably not working on plain win32 systems
  652. but may work on cygwin.
  653. """
  654. if isabs(name):
  655. p = py.path.local(name)
  656. if p.check(file=1):
  657. return p
  658. else:
  659. if paths is None:
  660. if iswin32:
  661. paths = os.environ['Path'].split(';')
  662. if '' not in paths and '.' not in paths:
  663. paths.append('.')
  664. try:
  665. systemroot = os.environ['SYSTEMROOT']
  666. except KeyError:
  667. pass
  668. else:
  669. paths = [path.replace('%SystemRoot%', systemroot)
  670. for path in paths]
  671. else:
  672. paths = os.environ['PATH'].split(':')
  673. tryadd = []
  674. if iswin32:
  675. tryadd += os.environ['PATHEXT'].split(os.pathsep)
  676. tryadd.append("")
  677. for x in paths:
  678. for addext in tryadd:
  679. p = py.path.local(x).join(name, abs=True) + addext
  680. try:
  681. if p.check(file=1):
  682. if checker:
  683. if not checker(p):
  684. continue
  685. return p
  686. except py.error.EACCES:
  687. pass
  688. return None
  689. sysfind = classmethod(sysfind)
  690. def _gethomedir(cls):
  691. try:
  692. x = os.environ['HOME']
  693. except KeyError:
  694. try:
  695. x = os.environ["HOMEDRIVE"] + os.environ['HOMEPATH']
  696. except KeyError:
  697. return None
  698. return cls(x)
  699. _gethomedir = classmethod(_gethomedir)
  700. # """
  701. # special class constructors for local filesystem paths
  702. # """
  703. @classmethod
  704. def get_temproot(cls):
  705. """ return the system's temporary directory
  706. (where tempfiles are usually created in)
  707. """
  708. import tempfile
  709. return py.path.local(tempfile.gettempdir())
  710. @classmethod
  711. def mkdtemp(cls, rootdir=None):
  712. """ return a Path object pointing to a fresh new temporary directory
  713. (which we created ourself).
  714. """
  715. import tempfile
  716. if rootdir is None:
  717. rootdir = cls.get_temproot()
  718. return cls(py.error.checked_call(tempfile.mkdtemp, dir=str(rootdir)))
  719. def make_numbered_dir(cls, prefix='session-', rootdir=None, keep=3,
  720. lock_timeout = 172800): # two days
  721. """ return unique directory with a number greater than the current
  722. maximum one. The number is assumed to start directly after prefix.
  723. if keep is true directories with a number less than (maxnum-keep)
  724. will be removed. If .lock files are used (lock_timeout non-zero),
  725. algorithm is multi-process safe.
  726. """
  727. if rootdir is None:
  728. rootdir = cls.get_temproot()
  729. nprefix = normcase(prefix)
  730. def parse_num(path):
  731. """ parse the number out of a path (if it matches the prefix) """
  732. nbasename = normcase(path.basename)
  733. if nbasename.startswith(nprefix):
  734. try:
  735. return int(nbasename[len(nprefix):])
  736. except ValueError:
  737. pass
  738. def create_lockfile(path):
  739. """ exclusively create lockfile. Throws when failed """
  740. mypid = os.getpid()
  741. lockfile = path.join('.lock')
  742. if hasattr(lockfile, 'mksymlinkto'):
  743. lockfile.mksymlinkto(str(mypid))
  744. else:
  745. fd = py.error.checked_call(os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
  746. with os.fdopen(fd, 'w') as f:
  747. f.write(str(mypid))
  748. return lockfile
  749. def atexit_remove_lockfile(lockfile):
  750. """ ensure lockfile is removed at process exit """
  751. mypid = os.getpid()
  752. def try_remove_lockfile():
  753. # in a fork() situation, only the last process should
  754. # remove the .lock, otherwise the other processes run the
  755. # risk of seeing their temporary dir disappear. For now
  756. # we remove the .lock in the parent only (i.e. we assume
  757. # that the children finish before the parent).
  758. if os.getpid() != mypid:
  759. return
  760. try:
  761. lockfile.remove()
  762. except py.error.Error:
  763. pass
  764. atexit.register(try_remove_lockfile)
  765. # compute the maximum number currently in use with the prefix
  766. lastmax = None
  767. while True:
  768. maxnum = -1
  769. for path in rootdir.listdir():
  770. num = parse_num(path)
  771. if num is not None:
  772. maxnum = max(maxnum, num)
  773. # make the new directory
  774. try:
  775. udir = rootdir.mkdir(prefix + str(maxnum+1))
  776. if lock_timeout:
  777. lockfile = create_lockfile(udir)
  778. atexit_remove_lockfile(lockfile)
  779. except (py.error.EEXIST, py.error.ENOENT, py.error.EBUSY):
  780. # race condition (1): another thread/process created the dir
  781. # in the meantime - try again
  782. # race condition (2): another thread/process spuriously acquired
  783. # lock treating empty directory as candidate
  784. # for removal - try again
  785. # race condition (3): another thread/process tried to create the lock at
  786. # the same time (happened in Python 3.3 on Windows)
  787. # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa
  788. if lastmax == maxnum:
  789. raise
  790. lastmax = maxnum
  791. continue
  792. break
  793. def get_mtime(path):
  794. """ read file modification time """
  795. try:
  796. return path.lstat().mtime
  797. except py.error.Error:
  798. pass
  799. garbage_prefix = prefix + 'garbage-'
  800. def is_garbage(path):
  801. """ check if path denotes directory scheduled for removal """
  802. bn = path.basename
  803. return bn.startswith(garbage_prefix)
  804. # prune old directories
  805. udir_time = get_mtime(udir)
  806. if keep and udir_time:
  807. for path in rootdir.listdir():
  808. num = parse_num(path)
  809. if num is not None and num <= (maxnum - keep):
  810. try:
  811. # try acquiring lock to remove directory as exclusive user
  812. if lock_timeout:
  813. create_lockfile(path)
  814. except (py.error.EEXIST, py.error.ENOENT, py.error.EBUSY):
  815. path_time = get_mtime(path)
  816. if not path_time:
  817. # assume directory doesn't exist now
  818. continue
  819. if abs(udir_time - path_time) < lock_timeout:
  820. # assume directory with lockfile exists
  821. # and lock timeout hasn't expired yet
  822. continue
  823. # path dir locked for exclusive use
  824. # and scheduled for removal to avoid another thread/process
  825. # treating it as a new directory or removal candidate
  826. garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4()))
  827. try:
  828. path.rename(garbage_path)
  829. garbage_path.remove(rec=1)
  830. except KeyboardInterrupt:
  831. raise
  832. except: # this might be py.error.Error, WindowsError ...
  833. pass
  834. if is_garbage(path):
  835. try:
  836. path.remove(rec=1)
  837. except KeyboardInterrupt:
  838. raise
  839. except: # this might be py.error.Error, WindowsError ...
  840. pass
  841. # make link...
  842. try:
  843. username = os.environ['USER'] #linux, et al
  844. except KeyError:
  845. try:
  846. username = os.environ['USERNAME'] #windows
  847. except KeyError:
  848. username = 'current'
  849. src = str(udir)
  850. dest = src[:src.rfind('-')] + '-' + username
  851. try:
  852. os.unlink(dest)
  853. except OSError:
  854. pass
  855. try:
  856. os.symlink(src, dest)
  857. except (OSError, AttributeError, NotImplementedError):
  858. pass
  859. return udir
  860. make_numbered_dir = classmethod(make_numbered_dir)
  861. def copymode(src, dest):
  862. """ copy permission from src to dst. """
  863. import shutil
  864. shutil.copymode(src, dest)
  865. def copystat(src, dest):
  866. """ copy permission, last modification time,
  867. last access time, and flags from src to dst."""
  868. import shutil
  869. shutil.copystat(str(src), str(dest))
  870. def copychunked(src, dest):
  871. chunksize = 524288 # half a meg of bytes
  872. fsrc = src.open('rb')
  873. try:
  874. fdest = dest.open('wb')
  875. try:
  876. while 1:
  877. buf = fsrc.read(chunksize)
  878. if not buf:
  879. break
  880. fdest.write(buf)
  881. finally:
  882. fdest.close()
  883. finally:
  884. fsrc.close()
  885. def isimportable(name):
  886. if name and (name[0].isalpha() or name[0] == '_'):
  887. name = name.replace("_", '')
  888. return not name or name.isalnum()