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.

704 lines
19KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2010 (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=0x1080e00
  12. """Constant updated on new releases"""
  13. WAFVERSION="1.8.14"
  14. """Constant updated on new releases"""
  15. WAFREVISION="ce8234c396bb246a20ea9f51594ee051d5b378e7"
  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. raise Errors.WafError('No wscript file in directory %s' % d)
  251. def exec_command(self, cmd, **kw):
  252. """
  253. Execute a command and return the exit status. If the context has the attribute 'log',
  254. capture and log the process stderr/stdout for logging purposes::
  255. def run(tsk):
  256. ret = tsk.generator.bld.exec_command('touch foo.txt')
  257. return ret
  258. This method captures the standard/error outputs (Issue 1101), but it does not return the values
  259. unlike :py:meth:`waflib.Context.Context.cmd_and_log`
  260. :param cmd: command argument for subprocess.Popen
  261. :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
  262. """
  263. subprocess = Utils.subprocess
  264. kw['shell'] = isinstance(cmd, str)
  265. Logs.debug('runner: %r' % (cmd,))
  266. Logs.debug('runner_env: kw=%s' % kw)
  267. if self.logger:
  268. self.logger.info(cmd)
  269. if 'stdout' not in kw:
  270. kw['stdout'] = subprocess.PIPE
  271. if 'stderr' not in kw:
  272. kw['stderr'] = subprocess.PIPE
  273. if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
  274. raise Errors.WafError("Program %s not found!" % cmd[0])
  275. wargs = {}
  276. if 'timeout' in kw:
  277. if kw['timeout'] is not None:
  278. wargs['timeout'] = kw['timeout']
  279. del kw['timeout']
  280. if 'input' in kw:
  281. if kw['input']:
  282. wargs['input'] = kw['input']
  283. kw['stdin'] = Utils.subprocess.PIPE
  284. del kw['input']
  285. try:
  286. if kw['stdout'] or kw['stderr']:
  287. p = subprocess.Popen(cmd, **kw)
  288. (out, err) = p.communicate(**wargs)
  289. ret = p.returncode
  290. else:
  291. out, err = (None, None)
  292. ret = subprocess.Popen(cmd, **kw).wait(**wargs)
  293. except Exception as e:
  294. raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
  295. if out:
  296. if not isinstance(out, str):
  297. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  298. if self.logger:
  299. self.logger.debug('out: %s' % out)
  300. else:
  301. Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
  302. if err:
  303. if not isinstance(err, str):
  304. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  305. if self.logger:
  306. self.logger.error('err: %s' % err)
  307. else:
  308. Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
  309. return ret
  310. def cmd_and_log(self, cmd, **kw):
  311. """
  312. Execute a command and return stdout/stderr if the execution is successful.
  313. An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
  314. will be bound to the WafError object::
  315. def configure(conf):
  316. out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
  317. (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
  318. (out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
  319. try:
  320. conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
  321. except Exception as e:
  322. print(e.stdout, e.stderr)
  323. :param cmd: args for subprocess.Popen
  324. :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
  325. """
  326. subprocess = Utils.subprocess
  327. kw['shell'] = isinstance(cmd, str)
  328. Logs.debug('runner: %r' % (cmd,))
  329. if 'quiet' in kw:
  330. quiet = kw['quiet']
  331. del kw['quiet']
  332. else:
  333. quiet = None
  334. if 'output' in kw:
  335. to_ret = kw['output']
  336. del kw['output']
  337. else:
  338. to_ret = STDOUT
  339. if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
  340. raise Errors.WafError("Program %s not found!" % cmd[0])
  341. kw['stdout'] = kw['stderr'] = subprocess.PIPE
  342. if quiet is None:
  343. self.to_log(cmd)
  344. wargs = {}
  345. if 'timeout' in kw:
  346. if kw['timeout'] is not None:
  347. wargs['timeout'] = kw['timeout']
  348. del kw['timeout']
  349. if 'input' in kw:
  350. if kw['input']:
  351. wargs['input'] = kw['input']
  352. kw['stdin'] = Utils.subprocess.PIPE
  353. del kw['input']
  354. try:
  355. p = subprocess.Popen(cmd, **kw)
  356. (out, err) = p.communicate(**wargs)
  357. except Exception as e:
  358. raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
  359. if not isinstance(out, str):
  360. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  361. if not isinstance(err, str):
  362. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  363. if out and quiet != STDOUT and quiet != BOTH:
  364. self.to_log('out: %s' % out)
  365. if err and quiet != STDERR and quiet != BOTH:
  366. self.to_log('err: %s' % err)
  367. if p.returncode:
  368. e = Errors.WafError('Command %r returned %r' % (cmd, p.returncode))
  369. e.returncode = p.returncode
  370. e.stderr = err
  371. e.stdout = out
  372. raise e
  373. if to_ret == BOTH:
  374. return (out, err)
  375. elif to_ret == STDERR:
  376. return err
  377. return out
  378. def fatal(self, msg, ex=None):
  379. """
  380. Raise a configuration error to interrupt the execution immediately::
  381. def configure(conf):
  382. conf.fatal('a requirement is missing')
  383. :param msg: message to display
  384. :type msg: string
  385. :param ex: optional exception object
  386. :type ex: exception
  387. """
  388. if self.logger:
  389. self.logger.info('from %s: %s' % (self.path.abspath(), msg))
  390. try:
  391. msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename)
  392. except Exception:
  393. pass
  394. raise self.errors.ConfigurationError(msg, ex=ex)
  395. def to_log(self, msg):
  396. """
  397. Log some information to the logger (if present), or to stderr. If the message is empty,
  398. it is not printed::
  399. def build(bld):
  400. bld.to_log('starting the build')
  401. When in doubt, override this method, or provide a logger on the context class.
  402. :param msg: message
  403. :type msg: string
  404. """
  405. if not msg:
  406. return
  407. if self.logger:
  408. self.logger.info(msg)
  409. else:
  410. sys.stderr.write(str(msg))
  411. sys.stderr.flush()
  412. def msg(self, *k, **kw):
  413. """
  414. Print a configuration message of the form ``msg: result``.
  415. The second part of the message will be in colors. The output
  416. can be disabled easly by setting ``in_msg`` to a positive value::
  417. def configure(conf):
  418. self.in_msg = 1
  419. conf.msg('Checking for library foo', 'ok')
  420. # no output
  421. :param msg: message to display to the user
  422. :type msg: string
  423. :param result: result to display
  424. :type result: string or boolean
  425. :param color: color to use, see :py:const:`waflib.Logs.colors_lst`
  426. :type color: string
  427. """
  428. try:
  429. msg = kw['msg']
  430. except KeyError:
  431. msg = k[0]
  432. self.start_msg(msg, **kw)
  433. try:
  434. result = kw['result']
  435. except KeyError:
  436. result = k[1]
  437. color = kw.get('color', None)
  438. if not isinstance(color, str):
  439. color = result and 'GREEN' or 'YELLOW'
  440. self.end_msg(result, color, **kw)
  441. def start_msg(self, *k, **kw):
  442. """
  443. Print the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
  444. """
  445. if kw.get('quiet', None):
  446. return
  447. msg = kw.get('msg', None) or k[0]
  448. try:
  449. if self.in_msg:
  450. self.in_msg += 1
  451. return
  452. except AttributeError:
  453. self.in_msg = 0
  454. self.in_msg += 1
  455. try:
  456. self.line_just = max(self.line_just, len(msg))
  457. except AttributeError:
  458. self.line_just = max(40, len(msg))
  459. for x in (self.line_just * '-', msg):
  460. self.to_log(x)
  461. Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
  462. def end_msg(self, *k, **kw):
  463. """Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
  464. if kw.get('quiet', None):
  465. return
  466. self.in_msg -= 1
  467. if self.in_msg:
  468. return
  469. result = kw.get('result', None) or k[0]
  470. defcolor = 'GREEN'
  471. if result == True:
  472. msg = 'ok'
  473. elif result == False:
  474. msg = 'not found'
  475. defcolor = 'YELLOW'
  476. else:
  477. msg = str(result)
  478. self.to_log(msg)
  479. try:
  480. color = kw['color']
  481. except KeyError:
  482. if len(k) > 1 and k[1] in Logs.colors_lst:
  483. # compatibility waf 1.7
  484. color = k[1]
  485. else:
  486. color = defcolor
  487. Logs.pprint(color, msg)
  488. def load_special_tools(self, var, ban=[]):
  489. global waf_dir
  490. if os.path.isdir(waf_dir):
  491. lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
  492. for x in lst:
  493. if not x.name in ban:
  494. load_tool(x.name.replace('.py', ''))
  495. else:
  496. from zipfile import PyZipFile
  497. waflibs = PyZipFile(waf_dir)
  498. lst = waflibs.namelist()
  499. for x in lst:
  500. if not re.match("waflib/extras/%s" % var.replace("*", ".*"), var):
  501. continue
  502. f = os.path.basename(x)
  503. doban = False
  504. for b in ban:
  505. r = b.replace("*", ".*")
  506. if re.match(b, f):
  507. doban = True
  508. if not doban:
  509. f = f.replace('.py', '')
  510. load_tool(f)
  511. cache_modules = {}
  512. """
  513. Dictionary holding already loaded modules, keyed by their absolute path.
  514. The modules are added automatically by :py:func:`waflib.Context.load_module`
  515. """
  516. def load_module(path, encoding=None):
  517. """
  518. Load a source file as a python module.
  519. :param path: file path
  520. :type path: string
  521. :return: Loaded Python module
  522. :rtype: module
  523. """
  524. try:
  525. return cache_modules[path]
  526. except KeyError:
  527. pass
  528. module = imp.new_module(WSCRIPT_FILE)
  529. try:
  530. code = Utils.readf(path, m='rU', encoding=encoding)
  531. except EnvironmentError:
  532. raise Errors.WafError('Could not read the file %r' % path)
  533. module_dir = os.path.dirname(path)
  534. sys.path.insert(0, module_dir)
  535. try : exec(compile(code, path, 'exec'), module.__dict__)
  536. finally: sys.path.remove(module_dir)
  537. cache_modules[path] = module
  538. return module
  539. def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
  540. """
  541. Import a Waf tool (python module), and store it in the dict :py:const:`waflib.Context.Context.tools`
  542. :type tool: string
  543. :param tool: Name of the tool
  544. :type tooldir: list
  545. :param tooldir: List of directories to search for the tool module
  546. :type with_sys_path: boolean
  547. :param with_sys_path: whether or not to search the regular sys.path, besides waf_dir and potentially given tooldirs
  548. """
  549. if tool == 'java':
  550. tool = 'javaw' # jython
  551. else:
  552. tool = tool.replace('++', 'xx')
  553. origSysPath = sys.path
  554. if not with_sys_path: sys.path = []
  555. try:
  556. if tooldir:
  557. assert isinstance(tooldir, list)
  558. sys.path = tooldir + sys.path
  559. try:
  560. __import__(tool)
  561. finally:
  562. for d in tooldir:
  563. sys.path.remove(d)
  564. ret = sys.modules[tool]
  565. Context.tools[tool] = ret
  566. return ret
  567. else:
  568. if not with_sys_path: sys.path.insert(0, waf_dir)
  569. try:
  570. for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
  571. try:
  572. __import__(x % tool)
  573. break
  574. except ImportError:
  575. x = None
  576. if x is None: # raise an exception
  577. __import__(tool)
  578. finally:
  579. if not with_sys_path: sys.path.remove(waf_dir)
  580. ret = sys.modules[x % tool]
  581. Context.tools[tool] = ret
  582. return ret
  583. finally:
  584. if not with_sys_path: sys.path += origSysPath