jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

708 lines
19KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2010-2016 (ita)
  4. """
  5. Classes and functions required for waf commands
  6. """
  7. import os, re, imp, sys
  8. from waflib import Utils, Errors, Logs
  9. import waflib.Node
  10. # the following 3 constants are updated on each new release (do not touch)
  11. HEXVERSION=0x1081600
  12. """Constant updated on new releases"""
  13. WAFVERSION="1.8.22"
  14. """Constant updated on new releases"""
  15. WAFREVISION="17d4d4faa52c454eb3580e482df69b2a80e19fa7"
  16. """Git revision when the waf version is updated"""
  17. ABI = 98
  18. """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
  19. DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
  20. """Name of the pickle file for storing the build data"""
  21. APPNAME = 'APPNAME'
  22. """Default application name (used by ``waf dist``)"""
  23. VERSION = 'VERSION'
  24. """Default application version (used by ``waf dist``)"""
  25. TOP = 'top'
  26. """The variable name for the top-level directory in wscript files"""
  27. OUT = 'out'
  28. """The variable name for the output directory in wscript files"""
  29. WSCRIPT_FILE = 'wscript'
  30. """Name of the waf script files"""
  31. launch_dir = ''
  32. """Directory from which waf has been called"""
  33. run_dir = ''
  34. """Location of the wscript file to use as the entry point"""
  35. top_dir = ''
  36. """Location of the project directory (top), if the project was configured"""
  37. out_dir = ''
  38. """Location of the build directory (out), if the project was configured"""
  39. waf_dir = ''
  40. """Directory containing the waf modules"""
  41. local_repo = ''
  42. """Local repository containing additional Waf tools (plugins)"""
  43. remote_repo = 'https://raw.githubusercontent.com/waf-project/waf/master/'
  44. """
  45. Remote directory containing downloadable waf tools. The missing tools can be downloaded by using::
  46. $ waf configure --download
  47. """
  48. remote_locs = ['waflib/extras', 'waflib/Tools']
  49. """
  50. Remote directories for use with :py:const:`waflib.Context.remote_repo`
  51. """
  52. g_module = None
  53. """
  54. Module representing the main wscript file (see :py:const:`waflib.Context.run_dir`)
  55. """
  56. STDOUT = 1
  57. STDERR = -1
  58. BOTH = 0
  59. classes = []
  60. """
  61. List of :py:class:`waflib.Context.Context` subclasses that can be used as waf commands. The classes
  62. are added automatically by a metaclass.
  63. """
  64. def create_context(cmd_name, *k, **kw):
  65. """
  66. Create a new :py:class:`waflib.Context.Context` instance corresponding to the given command.
  67. Used in particular by :py:func:`waflib.Scripting.run_command`
  68. :param cmd_name: command
  69. :type cmd_name: string
  70. :param k: arguments to give to the context class initializer
  71. :type k: list
  72. :param k: keyword arguments to give to the context class initializer
  73. :type k: dict
  74. """
  75. global classes
  76. for x in classes:
  77. if x.cmd == cmd_name:
  78. return x(*k, **kw)
  79. ctx = Context(*k, **kw)
  80. ctx.fun = cmd_name
  81. return ctx
  82. class store_context(type):
  83. """
  84. Metaclass for storing the command classes into the list :py:const:`waflib.Context.classes`
  85. Context classes must provide an attribute 'cmd' representing the command to execute
  86. """
  87. def __init__(cls, name, bases, dict):
  88. super(store_context, cls).__init__(name, bases, dict)
  89. name = cls.__name__
  90. if name == 'ctx' or name == 'Context':
  91. return
  92. try:
  93. cls.cmd
  94. except AttributeError:
  95. raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
  96. if not getattr(cls, 'fun', None):
  97. cls.fun = cls.cmd
  98. global classes
  99. classes.insert(0, cls)
  100. ctx = store_context('ctx', (object,), {})
  101. """Base class for the :py:class:`waflib.Context.Context` classes"""
  102. class Context(ctx):
  103. """
  104. Default context for waf commands, and base class for new command contexts.
  105. Context objects are passed to top-level functions::
  106. def foo(ctx):
  107. print(ctx.__class__.__name__) # waflib.Context.Context
  108. Subclasses must define the attribute 'cmd':
  109. :param cmd: command to execute as in ``waf cmd``
  110. :type cmd: string
  111. :param fun: function name to execute when the command is called
  112. :type fun: string
  113. .. inheritance-diagram:: waflib.Context.Context waflib.Build.BuildContext waflib.Build.InstallContext waflib.Build.UninstallContext waflib.Build.StepContext waflib.Build.ListContext waflib.Configure.ConfigurationContext waflib.Scripting.Dist waflib.Scripting.DistCheck waflib.Build.CleanContext
  114. """
  115. errors = Errors
  116. """
  117. Shortcut to :py:mod:`waflib.Errors` provided for convenience
  118. """
  119. tools = {}
  120. """
  121. A cache for modules (wscript files) read by :py:meth:`Context.Context.load`
  122. """
  123. def __init__(self, **kw):
  124. try:
  125. rd = kw['run_dir']
  126. except KeyError:
  127. global run_dir
  128. rd = run_dir
  129. # binds the context to the nodes in use to avoid a context singleton
  130. self.node_class = type("Nod3", (waflib.Node.Node,), {})
  131. self.node_class.__module__ = "waflib.Node"
  132. self.node_class.ctx = self
  133. self.root = self.node_class('', None)
  134. self.cur_script = None
  135. self.path = self.root.find_dir(rd)
  136. self.stack_path = []
  137. self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
  138. self.logger = None
  139. def __hash__(self):
  140. """
  141. Return a hash value for storing context objects in dicts or sets. The value is not persistent.
  142. :return: hash value
  143. :rtype: int
  144. """
  145. return id(self)
  146. def finalize(self):
  147. """
  148. Use to free resources such as open files potentially held by the logger
  149. """
  150. try:
  151. logger = self.logger
  152. except AttributeError:
  153. pass
  154. else:
  155. Logs.free_logger(logger)
  156. delattr(self, 'logger')
  157. def load(self, tool_list, *k, **kw):
  158. """
  159. Load a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun` from it.
  160. A ``tooldir`` value may be provided as a list of module paths.
  161. :type tool_list: list of string or space-separated string
  162. :param tool_list: list of Waf tools to use
  163. """
  164. tools = Utils.to_list(tool_list)
  165. path = Utils.to_list(kw.get('tooldir', ''))
  166. with_sys_path = kw.get('with_sys_path', True)
  167. for t in tools:
  168. module = load_tool(t, path, with_sys_path=with_sys_path)
  169. fun = getattr(module, kw.get('name', self.fun), None)
  170. if fun:
  171. fun(self)
  172. def execute(self):
  173. """
  174. Execute the command. Redefine this method in subclasses.
  175. """
  176. global g_module
  177. self.recurse([os.path.dirname(g_module.root_path)])
  178. def pre_recurse(self, node):
  179. """
  180. Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`. The node given is set
  181. as an attribute ``self.cur_script``, and as the current path ``self.path``
  182. :param node: script
  183. :type node: :py:class:`waflib.Node.Node`
  184. """
  185. self.stack_path.append(self.cur_script)
  186. self.cur_script = node
  187. self.path = node.parent
  188. def post_recurse(self, node):
  189. """
  190. Restore ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
  191. :param node: script
  192. :type node: :py:class:`waflib.Node.Node`
  193. """
  194. self.cur_script = self.stack_path.pop()
  195. if self.cur_script:
  196. self.path = self.cur_script.parent
  197. def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
  198. """
  199. Run user code from the supplied list of directories.
  200. The directories can be either absolute, or relative to the directory
  201. of the wscript file. The methods :py:meth:`waflib.Context.Context.pre_recurse` and :py:meth:`waflib.Context.Context.post_recurse`
  202. are called immediately before and after a script has been executed.
  203. :param dirs: List of directories to visit
  204. :type dirs: list of string or space-separated string
  205. :param name: Name of function to invoke from the wscript
  206. :type name: string
  207. :param mandatory: whether sub wscript files are required to exist
  208. :type mandatory: bool
  209. :param once: read the script file once for a particular context
  210. :type once: bool
  211. """
  212. try:
  213. cache = self.recurse_cache
  214. except AttributeError:
  215. cache = self.recurse_cache = {}
  216. for d in Utils.to_list(dirs):
  217. if not os.path.isabs(d):
  218. # absolute paths only
  219. d = os.path.join(self.path.abspath(), d)
  220. WSCRIPT = os.path.join(d, WSCRIPT_FILE)
  221. WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
  222. node = self.root.find_node(WSCRIPT_FUN)
  223. if node and (not once or node not in cache):
  224. cache[node] = True
  225. self.pre_recurse(node)
  226. try:
  227. function_code = node.read('rU', encoding)
  228. exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
  229. finally:
  230. self.post_recurse(node)
  231. elif not node:
  232. node = self.root.find_node(WSCRIPT)
  233. tup = (node, name or self.fun)
  234. if node and (not once or tup not in cache):
  235. cache[tup] = True
  236. self.pre_recurse(node)
  237. try:
  238. wscript_module = load_module(node.abspath(), encoding=encoding)
  239. user_function = getattr(wscript_module, (name or self.fun), None)
  240. if not user_function:
  241. if not mandatory:
  242. continue
  243. raise Errors.WafError('No function %s defined in %s' % (name or self.fun, node.abspath()))
  244. user_function(self)
  245. finally:
  246. self.post_recurse(node)
  247. elif not node:
  248. if not mandatory:
  249. continue
  250. try:
  251. os.listdir(d)
  252. except OSError:
  253. raise Errors.WafError('Cannot read the folder %r' % d)
  254. raise Errors.WafError('No wscript file in directory %s' % d)
  255. def exec_command(self, cmd, **kw):
  256. """
  257. Execute a command and return the exit status. If the context has the attribute 'log',
  258. capture and log the process stderr/stdout for logging purposes::
  259. def run(tsk):
  260. ret = tsk.generator.bld.exec_command('touch foo.txt')
  261. return ret
  262. This method captures the standard/error outputs (Issue 1101), but it does not return the values
  263. unlike :py:meth:`waflib.Context.Context.cmd_and_log`
  264. :param cmd: command argument for subprocess.Popen
  265. :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
  266. """
  267. subprocess = Utils.subprocess
  268. kw['shell'] = isinstance(cmd, str)
  269. Logs.debug('runner: %r' % (cmd,))
  270. Logs.debug('runner_env: kw=%s' % kw)
  271. if self.logger:
  272. self.logger.info(cmd)
  273. if 'stdout' not in kw:
  274. kw['stdout'] = subprocess.PIPE
  275. if 'stderr' not in kw:
  276. kw['stderr'] = subprocess.PIPE
  277. if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
  278. raise Errors.WafError("Program %s not found!" % cmd[0])
  279. wargs = {}
  280. if 'timeout' in kw:
  281. if kw['timeout'] is not None:
  282. wargs['timeout'] = kw['timeout']
  283. del kw['timeout']
  284. if 'input' in kw:
  285. if kw['input']:
  286. wargs['input'] = kw['input']
  287. kw['stdin'] = subprocess.PIPE
  288. del kw['input']
  289. try:
  290. if kw['stdout'] or kw['stderr']:
  291. p = subprocess.Popen(cmd, **kw)
  292. (out, err) = p.communicate(**wargs)
  293. ret = p.returncode
  294. else:
  295. out, err = (None, None)
  296. ret = subprocess.Popen(cmd, **kw).wait(**wargs)
  297. except Exception as e:
  298. raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
  299. if out:
  300. if not isinstance(out, str):
  301. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  302. if self.logger:
  303. self.logger.debug('out: %s' % out)
  304. else:
  305. Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
  306. if err:
  307. if not isinstance(err, str):
  308. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  309. if self.logger:
  310. self.logger.error('err: %s' % err)
  311. else:
  312. Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
  313. return ret
  314. def cmd_and_log(self, cmd, **kw):
  315. """
  316. Execute a command and return stdout/stderr if the execution is successful.
  317. An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
  318. will be bound to the WafError object::
  319. def configure(conf):
  320. out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
  321. (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
  322. (out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
  323. try:
  324. conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
  325. except Exception as e:
  326. print(e.stdout, e.stderr)
  327. :param cmd: args for subprocess.Popen
  328. :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
  329. """
  330. subprocess = Utils.subprocess
  331. kw['shell'] = isinstance(cmd, str)
  332. Logs.debug('runner: %r' % (cmd,))
  333. if 'quiet' in kw:
  334. quiet = kw['quiet']
  335. del kw['quiet']
  336. else:
  337. quiet = None
  338. if 'output' in kw:
  339. to_ret = kw['output']
  340. del kw['output']
  341. else:
  342. to_ret = STDOUT
  343. if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
  344. raise Errors.WafError("Program %s not found!" % cmd[0])
  345. kw['stdout'] = kw['stderr'] = subprocess.PIPE
  346. if quiet is None:
  347. self.to_log(cmd)
  348. wargs = {}
  349. if 'timeout' in kw:
  350. if kw['timeout'] is not None:
  351. wargs['timeout'] = kw['timeout']
  352. del kw['timeout']
  353. if 'input' in kw:
  354. if kw['input']:
  355. wargs['input'] = kw['input']
  356. kw['stdin'] = subprocess.PIPE
  357. del kw['input']
  358. try:
  359. p = subprocess.Popen(cmd, **kw)
  360. (out, err) = p.communicate(**wargs)
  361. except Exception as e:
  362. raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
  363. if not isinstance(out, str):
  364. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  365. if not isinstance(err, str):
  366. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  367. if out and quiet != STDOUT and quiet != BOTH:
  368. self.to_log('out: %s' % out)
  369. if err and quiet != STDERR and quiet != BOTH:
  370. self.to_log('err: %s' % err)
  371. if p.returncode:
  372. e = Errors.WafError('Command %r returned %r' % (cmd, p.returncode))
  373. e.returncode = p.returncode
  374. e.stderr = err
  375. e.stdout = out
  376. raise e
  377. if to_ret == BOTH:
  378. return (out, err)
  379. elif to_ret == STDERR:
  380. return err
  381. return out
  382. def fatal(self, msg, ex=None):
  383. """
  384. Raise a configuration error to interrupt the execution immediately::
  385. def configure(conf):
  386. conf.fatal('a requirement is missing')
  387. :param msg: message to display
  388. :type msg: string
  389. :param ex: optional exception object
  390. :type ex: exception
  391. """
  392. if self.logger:
  393. self.logger.info('from %s: %s' % (self.path.abspath(), msg))
  394. try:
  395. msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename)
  396. except Exception:
  397. pass
  398. raise self.errors.ConfigurationError(msg, ex=ex)
  399. def to_log(self, msg):
  400. """
  401. Log some information to the logger (if present), or to stderr. If the message is empty,
  402. it is not printed::
  403. def build(bld):
  404. bld.to_log('starting the build')
  405. When in doubt, override this method, or provide a logger on the context class.
  406. :param msg: message
  407. :type msg: string
  408. """
  409. if not msg:
  410. return
  411. if self.logger:
  412. self.logger.info(msg)
  413. else:
  414. sys.stderr.write(str(msg))
  415. sys.stderr.flush()
  416. def msg(self, *k, **kw):
  417. """
  418. Print a configuration message of the form ``msg: result``.
  419. The second part of the message will be in colors. The output
  420. can be disabled easly by setting ``in_msg`` to a positive value::
  421. def configure(conf):
  422. self.in_msg = 1
  423. conf.msg('Checking for library foo', 'ok')
  424. # no output
  425. :param msg: message to display to the user
  426. :type msg: string
  427. :param result: result to display
  428. :type result: string or boolean
  429. :param color: color to use, see :py:const:`waflib.Logs.colors_lst`
  430. :type color: string
  431. """
  432. try:
  433. msg = kw['msg']
  434. except KeyError:
  435. msg = k[0]
  436. self.start_msg(msg, **kw)
  437. try:
  438. result = kw['result']
  439. except KeyError:
  440. result = k[1]
  441. color = kw.get('color', None)
  442. if not isinstance(color, str):
  443. color = result and 'GREEN' or 'YELLOW'
  444. self.end_msg(result, color, **kw)
  445. def start_msg(self, *k, **kw):
  446. """
  447. Print the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
  448. """
  449. if kw.get('quiet', None):
  450. return
  451. msg = kw.get('msg', None) or k[0]
  452. try:
  453. if self.in_msg:
  454. self.in_msg += 1
  455. return
  456. except AttributeError:
  457. self.in_msg = 0
  458. self.in_msg += 1
  459. try:
  460. self.line_just = max(self.line_just, len(msg))
  461. except AttributeError:
  462. self.line_just = max(40, len(msg))
  463. for x in (self.line_just * '-', msg):
  464. self.to_log(x)
  465. Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
  466. def end_msg(self, *k, **kw):
  467. """Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
  468. if kw.get('quiet', None):
  469. return
  470. self.in_msg -= 1
  471. if self.in_msg:
  472. return
  473. result = kw.get('result', None) or k[0]
  474. defcolor = 'GREEN'
  475. if result == True:
  476. msg = 'ok'
  477. elif result == False:
  478. msg = 'not found'
  479. defcolor = 'YELLOW'
  480. else:
  481. msg = str(result)
  482. self.to_log(msg)
  483. try:
  484. color = kw['color']
  485. except KeyError:
  486. if len(k) > 1 and k[1] in Logs.colors_lst:
  487. # compatibility waf 1.7
  488. color = k[1]
  489. else:
  490. color = defcolor
  491. Logs.pprint(color, msg)
  492. def load_special_tools(self, var, ban=[]):
  493. global waf_dir
  494. if os.path.isdir(waf_dir):
  495. lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
  496. for x in lst:
  497. if not x.name in ban:
  498. load_tool(x.name.replace('.py', ''))
  499. else:
  500. from zipfile import PyZipFile
  501. waflibs = PyZipFile(waf_dir)
  502. lst = waflibs.namelist()
  503. for x in lst:
  504. if not re.match("waflib/extras/%s" % var.replace("*", ".*"), var):
  505. continue
  506. f = os.path.basename(x)
  507. doban = False
  508. for b in ban:
  509. r = b.replace("*", ".*")
  510. if re.match(r, f):
  511. doban = True
  512. if not doban:
  513. f = f.replace('.py', '')
  514. load_tool(f)
  515. cache_modules = {}
  516. """
  517. Dictionary holding already loaded modules, keyed by their absolute path.
  518. The modules are added automatically by :py:func:`waflib.Context.load_module`
  519. """
  520. def load_module(path, encoding=None):
  521. """
  522. Load a source file as a python module.
  523. :param path: file path
  524. :type path: string
  525. :return: Loaded Python module
  526. :rtype: module
  527. """
  528. try:
  529. return cache_modules[path]
  530. except KeyError:
  531. pass
  532. module = imp.new_module(WSCRIPT_FILE)
  533. try:
  534. code = Utils.readf(path, m='rU', encoding=encoding)
  535. except EnvironmentError:
  536. raise Errors.WafError('Could not read the file %r' % path)
  537. module_dir = os.path.dirname(path)
  538. sys.path.insert(0, module_dir)
  539. try : exec(compile(code, path, 'exec'), module.__dict__)
  540. finally: sys.path.remove(module_dir)
  541. cache_modules[path] = module
  542. return module
  543. def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
  544. """
  545. Import a Waf tool (python module), and store it in the dict :py:const:`waflib.Context.Context.tools`
  546. :type tool: string
  547. :param tool: Name of the tool
  548. :type tooldir: list
  549. :param tooldir: List of directories to search for the tool module
  550. :type with_sys_path: boolean
  551. :param with_sys_path: whether or not to search the regular sys.path, besides waf_dir and potentially given tooldirs
  552. """
  553. if tool == 'java':
  554. tool = 'javaw' # jython
  555. else:
  556. tool = tool.replace('++', 'xx')
  557. origSysPath = sys.path
  558. if not with_sys_path: sys.path = []
  559. try:
  560. if tooldir:
  561. assert isinstance(tooldir, list)
  562. sys.path = tooldir + sys.path
  563. try:
  564. __import__(tool)
  565. finally:
  566. for d in tooldir:
  567. sys.path.remove(d)
  568. ret = sys.modules[tool]
  569. Context.tools[tool] = ret
  570. return ret
  571. else:
  572. if not with_sys_path: sys.path.insert(0, waf_dir)
  573. try:
  574. for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
  575. try:
  576. __import__(x % tool)
  577. break
  578. except ImportError:
  579. x = None
  580. if x is None: # raise an exception
  581. __import__(tool)
  582. finally:
  583. if not with_sys_path: sys.path.remove(waf_dir)
  584. ret = sys.modules[x % tool]
  585. Context.tools[tool] = ret
  586. return ret
  587. finally:
  588. if not with_sys_path: sys.path += origSysPath