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.

668 lines
18KB

  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=0x1080900
  12. """Constant updated on new releases"""
  13. WAFVERSION="1.8.9"
  14. """Constant updated on new releases"""
  15. WAFREVISION="06e49b2a82166aeb14dde8357c58387f252fc722"
  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 = 'http://waf.googlecode.com/git/'
  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. for t in tools:
  167. module = load_tool(t, path)
  168. fun = getattr(module, kw.get('name', self.fun), None)
  169. if fun:
  170. fun(self)
  171. def execute(self):
  172. """
  173. Execute the command. Redefine this method in subclasses.
  174. """
  175. global g_module
  176. self.recurse([os.path.dirname(g_module.root_path)])
  177. def pre_recurse(self, node):
  178. """
  179. Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`. The node given is set
  180. as an attribute ``self.cur_script``, and as the current path ``self.path``
  181. :param node: script
  182. :type node: :py:class:`waflib.Node.Node`
  183. """
  184. self.stack_path.append(self.cur_script)
  185. self.cur_script = node
  186. self.path = node.parent
  187. def post_recurse(self, node):
  188. """
  189. Restore ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
  190. :param node: script
  191. :type node: :py:class:`waflib.Node.Node`
  192. """
  193. self.cur_script = self.stack_path.pop()
  194. if self.cur_script:
  195. self.path = self.cur_script.parent
  196. def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
  197. """
  198. Run user code from the supplied list of directories.
  199. The directories can be either absolute, or relative to the directory
  200. of the wscript file. The methods :py:meth:`waflib.Context.Context.pre_recurse` and :py:meth:`waflib.Context.Context.post_recurse`
  201. are called immediately before and after a script has been executed.
  202. :param dirs: List of directories to visit
  203. :type dirs: list of string or space-separated string
  204. :param name: Name of function to invoke from the wscript
  205. :type name: string
  206. :param mandatory: whether sub wscript files are required to exist
  207. :type mandatory: bool
  208. :param once: read the script file once for a particular context
  209. :type once: bool
  210. """
  211. try:
  212. cache = self.recurse_cache
  213. except AttributeError:
  214. cache = self.recurse_cache = {}
  215. for d in Utils.to_list(dirs):
  216. if not os.path.isabs(d):
  217. # absolute paths only
  218. d = os.path.join(self.path.abspath(), d)
  219. WSCRIPT = os.path.join(d, WSCRIPT_FILE)
  220. WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
  221. node = self.root.find_node(WSCRIPT_FUN)
  222. if node and (not once or node not in cache):
  223. cache[node] = True
  224. self.pre_recurse(node)
  225. try:
  226. function_code = node.read('rU', encoding)
  227. exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
  228. finally:
  229. self.post_recurse(node)
  230. elif not node:
  231. node = self.root.find_node(WSCRIPT)
  232. tup = (node, name or self.fun)
  233. if node and (not once or tup not in cache):
  234. cache[tup] = True
  235. self.pre_recurse(node)
  236. try:
  237. wscript_module = load_module(node.abspath(), encoding=encoding)
  238. user_function = getattr(wscript_module, (name or self.fun), None)
  239. if not user_function:
  240. if not mandatory:
  241. continue
  242. raise Errors.WafError('No function %s defined in %s' % (name or self.fun, node.abspath()))
  243. user_function(self)
  244. finally:
  245. self.post_recurse(node)
  246. elif not node:
  247. if not mandatory:
  248. continue
  249. raise Errors.WafError('No wscript file in directory %s' % d)
  250. def exec_command(self, cmd, **kw):
  251. """
  252. Execute a command and return the exit status. If the context has the attribute 'log',
  253. capture and log the process stderr/stdout for logging purposes::
  254. def run(tsk):
  255. ret = tsk.generator.bld.exec_command('touch foo.txt')
  256. return ret
  257. This method captures the standard/error outputs (Issue 1101), but it does not return the values
  258. unlike :py:meth:`waflib.Context.Context.cmd_and_log`
  259. :param cmd: command argument for subprocess.Popen
  260. :param kw: keyword arguments for subprocess.Popen
  261. """
  262. subprocess = Utils.subprocess
  263. kw['shell'] = isinstance(cmd, str)
  264. Logs.debug('runner: %r' % cmd)
  265. Logs.debug('runner_env: kw=%s' % kw)
  266. if self.logger:
  267. self.logger.info(cmd)
  268. if 'stdout' not in kw:
  269. kw['stdout'] = subprocess.PIPE
  270. if 'stderr' not in kw:
  271. kw['stderr'] = subprocess.PIPE
  272. if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
  273. raise Errors.WafError("Program %s not found!" % cmd[0])
  274. try:
  275. if kw['stdout'] or kw['stderr']:
  276. p = subprocess.Popen(cmd, **kw)
  277. (out, err) = p.communicate()
  278. ret = p.returncode
  279. else:
  280. out, err = (None, None)
  281. ret = subprocess.Popen(cmd, **kw).wait()
  282. except Exception as e:
  283. raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
  284. if out:
  285. if not isinstance(out, str):
  286. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  287. if self.logger:
  288. self.logger.debug('out: %s' % out)
  289. else:
  290. Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
  291. if err:
  292. if not isinstance(err, str):
  293. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  294. if self.logger:
  295. self.logger.error('err: %s' % err)
  296. else:
  297. Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
  298. return ret
  299. def cmd_and_log(self, cmd, **kw):
  300. """
  301. Execute a command and return stdout if the execution is successful.
  302. An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
  303. will be bound to the WafError object::
  304. def configure(conf):
  305. out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
  306. (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
  307. try:
  308. conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
  309. except Exception as e:
  310. print(e.stdout, e.stderr)
  311. :param cmd: args for subprocess.Popen
  312. :param kw: keyword arguments for subprocess.Popen
  313. """
  314. subprocess = Utils.subprocess
  315. kw['shell'] = isinstance(cmd, str)
  316. Logs.debug('runner: %r' % cmd)
  317. if 'quiet' in kw:
  318. quiet = kw['quiet']
  319. del kw['quiet']
  320. else:
  321. quiet = None
  322. if 'output' in kw:
  323. to_ret = kw['output']
  324. del kw['output']
  325. else:
  326. to_ret = STDOUT
  327. if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
  328. raise Errors.WafError("Program %s not found!" % cmd[0])
  329. kw['stdout'] = kw['stderr'] = subprocess.PIPE
  330. if quiet is None:
  331. self.to_log(cmd)
  332. try:
  333. p = subprocess.Popen(cmd, **kw)
  334. (out, err) = p.communicate()
  335. except Exception as e:
  336. raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
  337. if not isinstance(out, str):
  338. out = out.decode(sys.stdout.encoding or 'iso8859-1')
  339. if not isinstance(err, str):
  340. err = err.decode(sys.stdout.encoding or 'iso8859-1')
  341. if out and quiet != STDOUT and quiet != BOTH:
  342. self.to_log('out: %s' % out)
  343. if err and quiet != STDERR and quiet != BOTH:
  344. self.to_log('err: %s' % err)
  345. if p.returncode:
  346. e = Errors.WafError('Command %r returned %r' % (cmd, p.returncode))
  347. e.returncode = p.returncode
  348. e.stderr = err
  349. e.stdout = out
  350. raise e
  351. if to_ret == BOTH:
  352. return (out, err)
  353. elif to_ret == STDERR:
  354. return err
  355. return out
  356. def fatal(self, msg, ex=None):
  357. """
  358. Raise a configuration error to interrupt the execution immediately::
  359. def configure(conf):
  360. conf.fatal('a requirement is missing')
  361. :param msg: message to display
  362. :type msg: string
  363. :param ex: optional exception object
  364. :type ex: exception
  365. """
  366. if self.logger:
  367. self.logger.info('from %s: %s' % (self.path.abspath(), msg))
  368. try:
  369. msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename)
  370. except Exception:
  371. pass
  372. raise self.errors.ConfigurationError(msg, ex=ex)
  373. def to_log(self, msg):
  374. """
  375. Log some information to the logger (if present), or to stderr. If the message is empty,
  376. it is not printed::
  377. def build(bld):
  378. bld.to_log('starting the build')
  379. When in doubt, override this method, or provide a logger on the context class.
  380. :param msg: message
  381. :type msg: string
  382. """
  383. if not msg:
  384. return
  385. if self.logger:
  386. self.logger.info(msg)
  387. else:
  388. sys.stderr.write(str(msg))
  389. sys.stderr.flush()
  390. def msg(self, *k, **kw):
  391. """
  392. Print a configuration message of the form ``msg: result``.
  393. The second part of the message will be in colors. The output
  394. can be disabled easly by setting ``in_msg`` to a positive value::
  395. def configure(conf):
  396. self.in_msg = 1
  397. conf.msg('Checking for library foo', 'ok')
  398. # no output
  399. :param msg: message to display to the user
  400. :type msg: string
  401. :param result: result to display
  402. :type result: string or boolean
  403. :param color: color to use, see :py:const:`waflib.Logs.colors_lst`
  404. :type color: string
  405. """
  406. try:
  407. msg = kw['msg']
  408. except KeyError:
  409. msg = k[0]
  410. self.start_msg(msg, **kw)
  411. try:
  412. result = kw['result']
  413. except KeyError:
  414. result = k[1]
  415. color = kw.get('color', None)
  416. if not isinstance(color, str):
  417. color = result and 'GREEN' or 'YELLOW'
  418. self.end_msg(result, color, **kw)
  419. def start_msg(self, *k, **kw):
  420. """
  421. Print the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
  422. """
  423. if kw.get('quiet', None):
  424. return
  425. msg = kw.get('msg', None) or k[0]
  426. try:
  427. if self.in_msg:
  428. self.in_msg += 1
  429. return
  430. except AttributeError:
  431. self.in_msg = 0
  432. self.in_msg += 1
  433. try:
  434. self.line_just = max(self.line_just, len(msg))
  435. except AttributeError:
  436. self.line_just = max(40, len(msg))
  437. for x in (self.line_just * '-', msg):
  438. self.to_log(x)
  439. Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
  440. def end_msg(self, *k, **kw):
  441. """Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
  442. if kw.get('quiet', None):
  443. return
  444. self.in_msg -= 1
  445. if self.in_msg:
  446. return
  447. result = kw.get('result', None) or k[0]
  448. defcolor = 'GREEN'
  449. if result == True:
  450. msg = 'ok'
  451. elif result == False:
  452. msg = 'not found'
  453. defcolor = 'YELLOW'
  454. else:
  455. msg = str(result)
  456. self.to_log(msg)
  457. try:
  458. color = kw['color']
  459. except KeyError:
  460. if len(k) > 1 and k[1] in Logs.colors_lst:
  461. # compatibility waf 1.7
  462. color = k[1]
  463. else:
  464. color = defcolor
  465. Logs.pprint(color, msg)
  466. def load_special_tools(self, var, ban=[]):
  467. global waf_dir
  468. if os.path.isdir(waf_dir):
  469. lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
  470. for x in lst:
  471. if not x.name in ban:
  472. load_tool(x.name.replace('.py', ''))
  473. else:
  474. from zipfile import PyZipFile
  475. waflibs = PyZipFile(waf_dir)
  476. lst = waflibs.namelist()
  477. for x in lst:
  478. if not re.match("waflib/extras/%s" % var.replace("*", ".*"), var):
  479. continue
  480. f = os.path.basename(x)
  481. doban = False
  482. for b in ban:
  483. r = b.replace("*", ".*")
  484. if re.match(b, f):
  485. doban = True
  486. if not doban:
  487. f = f.replace('.py', '')
  488. load_tool(f)
  489. cache_modules = {}
  490. """
  491. Dictionary holding already loaded modules, keyed by their absolute path.
  492. The modules are added automatically by :py:func:`waflib.Context.load_module`
  493. """
  494. def load_module(path, encoding=None):
  495. """
  496. Load a source file as a python module.
  497. :param path: file path
  498. :type path: string
  499. :return: Loaded Python module
  500. :rtype: module
  501. """
  502. try:
  503. return cache_modules[path]
  504. except KeyError:
  505. pass
  506. module = imp.new_module(WSCRIPT_FILE)
  507. try:
  508. code = Utils.readf(path, m='rU', encoding=encoding)
  509. except EnvironmentError:
  510. raise Errors.WafError('Could not read the file %r' % path)
  511. module_dir = os.path.dirname(path)
  512. sys.path.insert(0, module_dir)
  513. exec(compile(code, path, 'exec'), module.__dict__)
  514. sys.path.remove(module_dir)
  515. cache_modules[path] = module
  516. return module
  517. def load_tool(tool, tooldir=None, ctx=None):
  518. """
  519. Import a Waf tool (python module), and store it in the dict :py:const:`waflib.Context.Context.tools`
  520. :type tool: string
  521. :param tool: Name of the tool
  522. :type tooldir: list
  523. :param tooldir: List of directories to search for the tool module
  524. """
  525. if tool == 'java':
  526. tool = 'javaw' # jython
  527. else:
  528. tool = tool.replace('++', 'xx')
  529. if tooldir:
  530. assert isinstance(tooldir, list)
  531. sys.path = tooldir + sys.path
  532. try:
  533. __import__(tool)
  534. ret = sys.modules[tool]
  535. Context.tools[tool] = ret
  536. return ret
  537. finally:
  538. for d in tooldir:
  539. sys.path.remove(d)
  540. else:
  541. for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
  542. try:
  543. __import__(x % tool)
  544. break
  545. except ImportError:
  546. x = None
  547. if x is None: # raise an exception
  548. __import__(tool)
  549. ret = sys.modules[x % tool]
  550. Context.tools[tool] = ret
  551. return ret