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.

1349 lines
37KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2010 (ita)
  4. """
  5. Classes related to the build phase (build, clean, install, step, etc)
  6. The inheritance tree is the following:
  7. """
  8. import os, sys, errno, re, shutil, stat
  9. try:
  10. import cPickle
  11. except ImportError:
  12. import pickle as cPickle
  13. from waflib import Runner, TaskGen, Utils, ConfigSet, Task, Logs, Options, Context, Errors
  14. import waflib.Node
  15. CACHE_DIR = 'c4che'
  16. """Location of the cache files"""
  17. CACHE_SUFFIX = '_cache.py'
  18. """Suffix for the cache files"""
  19. INSTALL = 1337
  20. """Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
  21. UNINSTALL = -1337
  22. """Negative value '<-' uninstall, see :py:attr:`waflib.Build.BuildContext.is_install`"""
  23. SAVED_ATTRS = 'root node_deps raw_deps task_sigs'.split()
  24. """Build class members to save between the runs (root, node_deps, raw_deps, task_sigs)"""
  25. CFG_FILES = 'cfg_files'
  26. """Files from the build directory to hash before starting the build (``config.h`` written during the configuration)"""
  27. POST_AT_ONCE = 0
  28. """Post mode: all task generators are posted before the build really starts"""
  29. POST_LAZY = 1
  30. """Post mode: post the task generators group after group"""
  31. POST_BOTH = 2
  32. """Post mode: post the task generators at once, then re-check them for each group"""
  33. PROTOCOL = -1
  34. if sys.platform == 'cli':
  35. PROTOCOL = 0
  36. class BuildContext(Context.Context):
  37. '''executes the build'''
  38. cmd = 'build'
  39. variant = ''
  40. def __init__(self, **kw):
  41. super(BuildContext, self).__init__(**kw)
  42. self.is_install = 0
  43. """Non-zero value when installing or uninstalling file"""
  44. self.top_dir = kw.get('top_dir', Context.top_dir)
  45. self.run_dir = kw.get('run_dir', Context.run_dir)
  46. self.post_mode = POST_AT_ONCE
  47. """post the task generators at once, group-by-group, or both"""
  48. # output directory - may be set until the nodes are considered
  49. self.out_dir = kw.get('out_dir', Context.out_dir)
  50. self.cache_dir = kw.get('cache_dir', None)
  51. if not self.cache_dir:
  52. self.cache_dir = os.path.join(self.out_dir, CACHE_DIR)
  53. # map names to environments, the '' must be defined
  54. self.all_envs = {}
  55. # ======================================= #
  56. # cache variables
  57. self.task_sigs = {}
  58. """Signatures of the tasks (persists between build executions)"""
  59. self.node_deps = {}
  60. """Dict of node dependencies found by :py:meth:`waflib.Task.Task.scan` (persists between build executions)"""
  61. self.raw_deps = {}
  62. """Dict of custom data returned by :py:meth:`waflib.Task.Task.scan` (persists between build executions)"""
  63. # list of folders that are already scanned
  64. # so that we do not need to stat them one more time
  65. self.cache_dir_contents = {}
  66. self.task_gen_cache_names = {}
  67. self.launch_dir = Context.launch_dir
  68. self.jobs = Options.options.jobs
  69. self.targets = Options.options.targets
  70. self.keep = Options.options.keep
  71. self.progress_bar = Options.options.progress_bar
  72. ############ stuff below has not been reviewed
  73. # Manual dependencies.
  74. self.deps_man = Utils.defaultdict(list)
  75. """Manual dependencies set by :py:meth:`waflib.Build.BuildContext.add_manual_dependency`"""
  76. # just the structure here
  77. self.current_group = 0
  78. """
  79. Current build group
  80. """
  81. self.groups = []
  82. """
  83. List containing lists of task generators
  84. """
  85. self.group_names = {}
  86. """
  87. Map group names to the group lists. See :py:meth:`waflib.Build.BuildContext.add_group`
  88. """
  89. def get_variant_dir(self):
  90. """Getter for the variant_dir attribute"""
  91. if not self.variant:
  92. return self.out_dir
  93. return os.path.join(self.out_dir, self.variant)
  94. variant_dir = property(get_variant_dir, None)
  95. def __call__(self, *k, **kw):
  96. """
  97. Create a task generator and add it to the current build group. The following forms are equivalent::
  98. def build(bld):
  99. tg = bld(a=1, b=2)
  100. def build(bld):
  101. tg = bld()
  102. tg.a = 1
  103. tg.b = 2
  104. def build(bld):
  105. tg = TaskGen.task_gen(a=1, b=2)
  106. bld.add_to_group(tg, None)
  107. :param group: group name to add the task generator to
  108. :type group: string
  109. """
  110. kw['bld'] = self
  111. ret = TaskGen.task_gen(*k, **kw)
  112. self.task_gen_cache_names = {} # reset the cache, each time
  113. self.add_to_group(ret, group=kw.get('group', None))
  114. return ret
  115. def rule(self, *k, **kw):
  116. """
  117. Wrapper for creating a task generator using the decorator notation. The following code::
  118. @bld.rule(
  119. target = "foo"
  120. )
  121. def _(tsk):
  122. print("bar")
  123. is equivalent to::
  124. def bar(tsk):
  125. print("bar")
  126. bld(
  127. target = "foo",
  128. rule = bar,
  129. )
  130. """
  131. def f(rule):
  132. ret = self(*k, **kw)
  133. ret.rule = rule
  134. return ret
  135. return f
  136. def __copy__(self):
  137. """Implemented to prevents copies of build contexts (raises an exception)"""
  138. raise Errors.WafError('build contexts are not supposed to be copied')
  139. def install_files(self, *k, **kw):
  140. """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.install_files`"""
  141. pass
  142. def install_as(self, *k, **kw):
  143. """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.install_as`"""
  144. pass
  145. def symlink_as(self, *k, **kw):
  146. """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.symlink_as`"""
  147. pass
  148. def load_envs(self):
  149. """
  150. The configuration command creates files of the form ``build/c4che/NAMEcache.py``. This method
  151. creates a :py:class:`waflib.ConfigSet.ConfigSet` instance for each ``NAME`` by reading those
  152. files. The config sets are then stored in the dict :py:attr:`waflib.Build.BuildContext.allenvs`.
  153. """
  154. node = self.root.find_node(self.cache_dir)
  155. if not node:
  156. raise Errors.WafError('The project was not configured: run "waf configure" first!')
  157. lst = node.ant_glob('**/*%s' % CACHE_SUFFIX, quiet=True)
  158. if not lst:
  159. raise Errors.WafError('The cache directory is empty: reconfigure the project')
  160. for x in lst:
  161. name = x.path_from(node).replace(CACHE_SUFFIX, '').replace('\\', '/')
  162. env = ConfigSet.ConfigSet(x.abspath())
  163. self.all_envs[name] = env
  164. for f in env[CFG_FILES]:
  165. newnode = self.root.find_resource(f)
  166. try:
  167. h = Utils.h_file(newnode.abspath())
  168. except (IOError, AttributeError):
  169. Logs.error('cannot find %r' % f)
  170. h = Utils.SIG_NIL
  171. newnode.sig = h
  172. def init_dirs(self):
  173. """
  174. Initialize the project directory and the build directory by creating the nodes
  175. :py:attr:`waflib.Build.BuildContext.srcnode` and :py:attr:`waflib.Build.BuildContext.bldnode`
  176. corresponding to ``top_dir`` and ``variant_dir`` respectively. The ``bldnode`` directory will be
  177. created if it does not exist.
  178. """
  179. if not (os.path.isabs(self.top_dir) and os.path.isabs(self.out_dir)):
  180. raise Errors.WafError('The project was not configured: run "waf configure" first!')
  181. self.path = self.srcnode = self.root.find_dir(self.top_dir)
  182. self.bldnode = self.root.make_node(self.variant_dir)
  183. self.bldnode.mkdir()
  184. def execute(self):
  185. """
  186. Restore the data from previous builds and call :py:meth:`waflib.Build.BuildContext.execute_build`. Overrides from :py:func:`waflib.Context.Context.execute`
  187. """
  188. self.restore()
  189. if not self.all_envs:
  190. self.load_envs()
  191. self.execute_build()
  192. def execute_build(self):
  193. """
  194. Execute the build by:
  195. * reading the scripts (see :py:meth:`waflib.Context.Context.recurse`)
  196. * calling :py:meth:`waflib.Build.BuildContext.pre_build` to call user build functions
  197. * calling :py:meth:`waflib.Build.BuildContext.compile` to process the tasks
  198. * calling :py:meth:`waflib.Build.BuildContext.post_build` to call user build functions
  199. """
  200. Logs.info("Waf: Entering directory `%s'" % self.variant_dir)
  201. self.recurse([self.run_dir])
  202. self.pre_build()
  203. # display the time elapsed in the progress bar
  204. self.timer = Utils.Timer()
  205. try:
  206. self.compile()
  207. finally:
  208. if self.progress_bar == 1 and sys.stderr.isatty():
  209. c = len(self.returned_tasks) or 1
  210. m = self.progress_line(c, c, Logs.colors.BLUE, Logs.colors.NORMAL)
  211. Logs.info(m, extra={'stream': sys.stderr, 'c1': Logs.colors.cursor_off, 'c2' : Logs.colors.cursor_on})
  212. Logs.info("Waf: Leaving directory `%s'" % self.variant_dir)
  213. self.post_build()
  214. def restore(self):
  215. """
  216. Load the data from a previous run, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`
  217. """
  218. try:
  219. env = ConfigSet.ConfigSet(os.path.join(self.cache_dir, 'build.config.py'))
  220. except EnvironmentError:
  221. pass
  222. else:
  223. if env['version'] < Context.HEXVERSION:
  224. raise Errors.WafError('Version mismatch! reconfigure the project')
  225. for t in env['tools']:
  226. self.setup(**t)
  227. dbfn = os.path.join(self.variant_dir, Context.DBFILE)
  228. try:
  229. data = Utils.readf(dbfn, 'rb')
  230. except (IOError, EOFError):
  231. # handle missing file/empty file
  232. Logs.debug('build: Could not load the build cache %s (missing)' % dbfn)
  233. else:
  234. try:
  235. waflib.Node.pickle_lock.acquire()
  236. waflib.Node.Nod3 = self.node_class
  237. try:
  238. data = cPickle.loads(data)
  239. except Exception as e:
  240. Logs.debug('build: Could not pickle the build cache %s: %r' % (dbfn, e))
  241. else:
  242. for x in SAVED_ATTRS:
  243. setattr(self, x, data[x])
  244. finally:
  245. waflib.Node.pickle_lock.release()
  246. self.init_dirs()
  247. def store(self):
  248. """
  249. Store the data for next runs, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`. Uses a temporary
  250. file to avoid problems on ctrl+c.
  251. """
  252. data = {}
  253. for x in SAVED_ATTRS:
  254. data[x] = getattr(self, x)
  255. db = os.path.join(self.variant_dir, Context.DBFILE)
  256. try:
  257. waflib.Node.pickle_lock.acquire()
  258. waflib.Node.Nod3 = self.node_class
  259. x = cPickle.dumps(data, PROTOCOL)
  260. finally:
  261. waflib.Node.pickle_lock.release()
  262. Utils.writef(db + '.tmp', x, m='wb')
  263. try:
  264. st = os.stat(db)
  265. os.remove(db)
  266. if not Utils.is_win32: # win32 has no chown but we're paranoid
  267. os.chown(db + '.tmp', st.st_uid, st.st_gid)
  268. except (AttributeError, OSError):
  269. pass
  270. # do not use shutil.move (copy is not thread-safe)
  271. os.rename(db + '.tmp', db)
  272. def compile(self):
  273. """
  274. Run the build by creating an instance of :py:class:`waflib.Runner.Parallel`
  275. The cache file is not written if the build is up to date (no task executed).
  276. """
  277. Logs.debug('build: compile()')
  278. # use another object to perform the producer-consumer logic (reduce the complexity)
  279. self.producer = Runner.Parallel(self, self.jobs)
  280. self.producer.biter = self.get_build_iterator()
  281. self.returned_tasks = [] # not part of the API yet
  282. try:
  283. self.producer.start()
  284. except KeyboardInterrupt:
  285. self.store()
  286. raise
  287. else:
  288. if self.producer.dirty:
  289. self.store()
  290. if self.producer.error:
  291. raise Errors.BuildError(self.producer.error)
  292. def setup(self, tool, tooldir=None, funs=None):
  293. """
  294. Import waf tools, used to import those accessed during the configuration::
  295. def configure(conf):
  296. conf.load('glib2')
  297. def build(bld):
  298. pass # glib2 is imported implicitly
  299. :param tool: tool list
  300. :type tool: list
  301. :param tooldir: optional tool directory (sys.path)
  302. :type tooldir: list of string
  303. :param funs: unused variable
  304. """
  305. if isinstance(tool, list):
  306. for i in tool: self.setup(i, tooldir)
  307. return
  308. module = Context.load_tool(tool, tooldir)
  309. if hasattr(module, "setup"): module.setup(self)
  310. def get_env(self):
  311. """Getter for the env property"""
  312. try:
  313. return self.all_envs[self.variant]
  314. except KeyError:
  315. return self.all_envs['']
  316. def set_env(self, val):
  317. """Setter for the env property"""
  318. self.all_envs[self.variant] = val
  319. env = property(get_env, set_env)
  320. def add_manual_dependency(self, path, value):
  321. """
  322. Adds a dependency from a node object to a value::
  323. def build(bld):
  324. bld.add_manual_dependency(
  325. bld.path.find_resource('wscript'),
  326. bld.root.find_resource('/etc/fstab'))
  327. :param path: file path
  328. :type path: string or :py:class:`waflib.Node.Node`
  329. :param value: value to depend on
  330. :type value: :py:class:`waflib.Node.Node`, string, or function returning a string
  331. """
  332. if path is None:
  333. raise ValueError('Invalid input')
  334. if isinstance(path, waflib.Node.Node):
  335. node = path
  336. elif os.path.isabs(path):
  337. node = self.root.find_resource(path)
  338. else:
  339. node = self.path.find_resource(path)
  340. if isinstance(value, list):
  341. self.deps_man[id(node)].extend(value)
  342. else:
  343. self.deps_man[id(node)].append(value)
  344. def launch_node(self):
  345. """Returns the launch directory as a :py:class:`waflib.Node.Node` object"""
  346. try:
  347. # private cache
  348. return self.p_ln
  349. except AttributeError:
  350. self.p_ln = self.root.find_dir(self.launch_dir)
  351. return self.p_ln
  352. def hash_env_vars(self, env, vars_lst):
  353. """
  354. Hash configuration set variables::
  355. def build(bld):
  356. bld.hash_env_vars(bld.env, ['CXX', 'CC'])
  357. :param env: Configuration Set
  358. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  359. :param vars_lst: list of variables
  360. :type vars_list: list of string
  361. """
  362. if not env.table:
  363. env = env.parent
  364. if not env:
  365. return Utils.SIG_NIL
  366. idx = str(id(env)) + str(vars_lst)
  367. try:
  368. cache = self.cache_env
  369. except AttributeError:
  370. cache = self.cache_env = {}
  371. else:
  372. try:
  373. return self.cache_env[idx]
  374. except KeyError:
  375. pass
  376. lst = [env[a] for a in vars_lst]
  377. ret = Utils.h_list(lst)
  378. Logs.debug('envhash: %s %r', Utils.to_hex(ret), lst)
  379. cache[idx] = ret
  380. return ret
  381. def get_tgen_by_name(self, name):
  382. """
  383. Retrieves a task generator from its name or its target name
  384. the name must be unique::
  385. def build(bld):
  386. tg = bld(name='foo')
  387. tg == bld.get_tgen_by_name('foo')
  388. """
  389. cache = self.task_gen_cache_names
  390. if not cache:
  391. # create the index lazily
  392. for g in self.groups:
  393. for tg in g:
  394. try:
  395. cache[tg.name] = tg
  396. except AttributeError:
  397. # raised if not a task generator, which should be uncommon
  398. pass
  399. try:
  400. return cache[name]
  401. except KeyError:
  402. raise Errors.WafError('Could not find a task generator for the name %r' % name)
  403. def progress_line(self, state, total, col1, col2):
  404. """
  405. Compute the progress bar used by ``waf -p``
  406. """
  407. if not sys.stderr.isatty():
  408. return ''
  409. n = len(str(total))
  410. Utils.rot_idx += 1
  411. ind = Utils.rot_chr[Utils.rot_idx % 4]
  412. pc = (100.*state)/total
  413. eta = str(self.timer)
  414. fs = "[%%%dd/%%%dd][%%s%%2d%%%%%%s][%s][" % (n, n, ind)
  415. left = fs % (state, total, col1, pc, col2)
  416. right = '][%s%s%s]' % (col1, eta, col2)
  417. cols = Logs.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2)
  418. if cols < 7: cols = 7
  419. ratio = ((cols*state)//total) - 1
  420. bar = ('='*ratio+'>').ljust(cols)
  421. msg = Logs.indicator % (left, bar, right)
  422. return msg
  423. def declare_chain(self, *k, **kw):
  424. """
  425. Wrapper for :py:func:`waflib.TaskGen.declare_chain` provided for convenience
  426. """
  427. return TaskGen.declare_chain(*k, **kw)
  428. def pre_build(self):
  429. """Execute user-defined methods before the build starts, see :py:meth:`waflib.Build.BuildContext.add_pre_fun`"""
  430. for m in getattr(self, 'pre_funs', []):
  431. m(self)
  432. def post_build(self):
  433. """Executes the user-defined methods after the build is successful, see :py:meth:`waflib.Build.BuildContext.add_post_fun`"""
  434. for m in getattr(self, 'post_funs', []):
  435. m(self)
  436. def add_pre_fun(self, meth):
  437. """
  438. Bind a method to execute after the scripts are read and before the build starts::
  439. def mycallback(bld):
  440. print("Hello, world!")
  441. def build(bld):
  442. bld.add_pre_fun(mycallback)
  443. """
  444. try:
  445. self.pre_funs.append(meth)
  446. except AttributeError:
  447. self.pre_funs = [meth]
  448. def add_post_fun(self, meth):
  449. """
  450. Bind a method to execute immediately after the build is successful::
  451. def call_ldconfig(bld):
  452. bld.exec_command('/sbin/ldconfig')
  453. def build(bld):
  454. if bld.cmd == 'install':
  455. bld.add_pre_fun(call_ldconfig)
  456. """
  457. try:
  458. self.post_funs.append(meth)
  459. except AttributeError:
  460. self.post_funs = [meth]
  461. def get_group(self, x):
  462. """
  463. Get the group x, or return the current group if x is None
  464. :param x: name or number or None
  465. :type x: string, int or None
  466. """
  467. if not self.groups:
  468. self.add_group()
  469. if x is None:
  470. return self.groups[self.current_group]
  471. if x in self.group_names:
  472. return self.group_names[x]
  473. return self.groups[x]
  474. def add_to_group(self, tgen, group=None):
  475. """add a task or a task generator for the build"""
  476. # paranoid
  477. assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.TaskBase))
  478. tgen.bld = self
  479. self.get_group(group).append(tgen)
  480. def get_group_name(self, g):
  481. """name for the group g (utility)"""
  482. if not isinstance(g, list):
  483. g = self.groups[g]
  484. for x in self.group_names:
  485. if id(self.group_names[x]) == id(g):
  486. return x
  487. return ''
  488. def get_group_idx(self, tg):
  489. """
  490. Index of the group containing the task generator given as argument::
  491. def build(bld):
  492. tg = bld(name='nada')
  493. 0 == bld.get_group_idx(tg)
  494. :param tg: Task generator object
  495. :type tg: :py:class:`waflib.TaskGen.task_gen`
  496. """
  497. se = id(tg)
  498. for i in range(len(self.groups)):
  499. for t in self.groups[i]:
  500. if id(t) == se:
  501. return i
  502. return None
  503. def add_group(self, name=None, move=True):
  504. """
  505. Add a new group of tasks/task generators. By default the new group becomes the default group for new task generators.
  506. :param name: name for this group
  507. :type name: string
  508. :param move: set the group created as default group (True by default)
  509. :type move: bool
  510. """
  511. #if self.groups and not self.groups[0].tasks:
  512. # error('add_group: an empty group is already present')
  513. if name and name in self.group_names:
  514. Logs.error('add_group: name %s already present' % name)
  515. g = []
  516. self.group_names[name] = g
  517. self.groups.append(g)
  518. if move:
  519. self.current_group = len(self.groups) - 1
  520. def set_group(self, idx):
  521. """
  522. Set the current group to be idx: now new task generators will be added to this group by default::
  523. def build(bld):
  524. bld(rule='touch ${TGT}', target='foo.txt')
  525. bld.add_group() # now the current group is 1
  526. bld(rule='touch ${TGT}', target='bar.txt')
  527. bld.set_group(0) # now the current group is 0
  528. bld(rule='touch ${TGT}', target='truc.txt') # build truc.txt before bar.txt
  529. :param idx: group name or group index
  530. :type idx: string or int
  531. """
  532. if isinstance(idx, str):
  533. g = self.group_names[idx]
  534. for i in range(len(self.groups)):
  535. if id(g) == id(self.groups[i]):
  536. self.current_group = i
  537. break
  538. else:
  539. self.current_group = idx
  540. def total(self):
  541. """
  542. Approximate task count: this value may be inaccurate if task generators are posted lazily (see :py:attr:`waflib.Build.BuildContext.post_mode`).
  543. The value :py:attr:`waflib.Runner.Parallel.total` is updated during the task execution.
  544. """
  545. total = 0
  546. for group in self.groups:
  547. for tg in group:
  548. try:
  549. total += len(tg.tasks)
  550. except AttributeError:
  551. total += 1
  552. return total
  553. def get_targets(self):
  554. """
  555. Return the task generator corresponding to the 'targets' list, used by :py:meth:`waflib.Build.BuildContext.get_build_iterator`::
  556. $ waf --targets=myprogram,myshlib
  557. """
  558. to_post = []
  559. min_grp = 0
  560. for name in self.targets.split(','):
  561. tg = self.get_tgen_by_name(name)
  562. m = self.get_group_idx(tg)
  563. if m > min_grp:
  564. min_grp = m
  565. to_post = [tg]
  566. elif m == min_grp:
  567. to_post.append(tg)
  568. return (min_grp, to_post)
  569. def get_all_task_gen(self):
  570. """
  571. Utility method, returns a list of all task generators - if you need something more complicated, implement your own
  572. """
  573. lst = []
  574. for g in self.groups:
  575. lst.extend(g)
  576. return lst
  577. def post_group(self):
  578. """
  579. Post the task generators from the group indexed by self.cur, used by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
  580. """
  581. if self.targets == '*':
  582. for tg in self.groups[self.cur]:
  583. try:
  584. f = tg.post
  585. except AttributeError:
  586. pass
  587. else:
  588. f()
  589. elif self.targets:
  590. if self.cur < self._min_grp:
  591. for tg in self.groups[self.cur]:
  592. try:
  593. f = tg.post
  594. except AttributeError:
  595. pass
  596. else:
  597. f()
  598. else:
  599. for tg in self._exact_tg:
  600. tg.post()
  601. else:
  602. ln = self.launch_node()
  603. if ln.is_child_of(self.bldnode):
  604. Logs.warn('Building from the build directory, forcing --targets=*')
  605. ln = self.srcnode
  606. elif not ln.is_child_of(self.srcnode):
  607. Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)' % (ln.abspath(), self.srcnode.abspath()))
  608. ln = self.srcnode
  609. for tg in self.groups[self.cur]:
  610. try:
  611. f = tg.post
  612. except AttributeError:
  613. pass
  614. else:
  615. if tg.path.is_child_of(ln):
  616. f()
  617. def get_tasks_group(self, idx):
  618. """
  619. Return all the tasks for the group of num idx, used by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
  620. """
  621. tasks = []
  622. for tg in self.groups[idx]:
  623. try:
  624. tasks.extend(tg.tasks)
  625. except AttributeError: # not a task generator, can be the case for installation tasks
  626. tasks.append(tg)
  627. return tasks
  628. def get_build_iterator(self):
  629. """
  630. Creates a generator object that returns lists of tasks executable in parallel (yield)
  631. :return: tasks which can be executed immediatly
  632. :rtype: list of :py:class:`waflib.Task.TaskBase`
  633. """
  634. self.cur = 0
  635. if self.targets and self.targets != '*':
  636. (self._min_grp, self._exact_tg) = self.get_targets()
  637. global lazy_post
  638. if self.post_mode != POST_LAZY:
  639. while self.cur < len(self.groups):
  640. self.post_group()
  641. self.cur += 1
  642. self.cur = 0
  643. while self.cur < len(self.groups):
  644. # first post the task generators for the group
  645. if self.post_mode != POST_AT_ONCE:
  646. self.post_group()
  647. # then extract the tasks
  648. tasks = self.get_tasks_group(self.cur)
  649. # if the constraints are set properly (ext_in/ext_out, before/after)
  650. # the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
  651. # (but leave set_file_constraints for the installation step)
  652. #
  653. # if the tasks have only files, set_file_constraints is required but set_precedence_constraints is not necessary
  654. #
  655. Task.set_file_constraints(tasks)
  656. Task.set_precedence_constraints(tasks)
  657. self.cur_tasks = tasks
  658. self.cur += 1
  659. if not tasks: # return something else the build will stop
  660. continue
  661. yield tasks
  662. while 1:
  663. yield []
  664. class inst(Task.Task):
  665. """
  666. Special task used for installing files and symlinks, it behaves both like a task
  667. and like a task generator
  668. """
  669. color = 'CYAN'
  670. def uid(self):
  671. lst = [self.dest, self.path] + self.source
  672. return Utils.h_list(repr(lst))
  673. def post(self):
  674. """
  675. Same interface as in :py:meth:`waflib.TaskGen.task_gen.post`
  676. """
  677. buf = []
  678. for x in self.source:
  679. if isinstance(x, waflib.Node.Node):
  680. y = x
  681. else:
  682. y = self.path.find_resource(x)
  683. if not y:
  684. if os.path.isabs(x):
  685. y = self.bld.root.make_node(x)
  686. else:
  687. y = self.path.make_node(x)
  688. buf.append(y)
  689. self.inputs = buf
  690. def runnable_status(self):
  691. """
  692. Installation tasks are always executed, so this method returns either :py:const:`waflib.Task.ASK_LATER` or :py:const:`waflib.Task.RUN_ME`.
  693. """
  694. ret = super(inst, self).runnable_status()
  695. if ret == Task.SKIP_ME:
  696. return Task.RUN_ME
  697. return ret
  698. def __str__(self):
  699. """Return an empty string to disable the display"""
  700. return ''
  701. def run(self):
  702. """The attribute 'exec_task' holds the method to execute"""
  703. return self.generator.exec_task()
  704. def get_install_path(self, destdir=True):
  705. """
  706. Installation path obtained from ``self.dest`` and prefixed by the destdir.
  707. The variables such as '${PREFIX}/bin' are substituted.
  708. """
  709. dest = Utils.subst_vars(self.dest, self.env)
  710. dest = dest.replace('/', os.sep)
  711. if destdir and Options.options.destdir:
  712. dest = os.path.join(Options.options.destdir, os.path.splitdrive(dest)[1].lstrip(os.sep))
  713. return dest
  714. def exec_install_files(self):
  715. """
  716. Predefined method for installing files
  717. """
  718. destpath = self.get_install_path()
  719. if not destpath:
  720. raise Errors.WafError('unknown installation path %r' % self.generator)
  721. for x, y in zip(self.source, self.inputs):
  722. if self.relative_trick:
  723. destfile = os.path.join(destpath, y.path_from(self.path))
  724. else:
  725. destfile = os.path.join(destpath, y.name)
  726. self.generator.bld.do_install(y.abspath(), destfile, chmod=self.chmod, tsk=self)
  727. def exec_install_as(self):
  728. """
  729. Predefined method for installing one file with a given name
  730. """
  731. destfile = self.get_install_path()
  732. self.generator.bld.do_install(self.inputs[0].abspath(), destfile, chmod=self.chmod, tsk=self)
  733. def exec_symlink_as(self):
  734. """
  735. Predefined method for installing a symlink
  736. """
  737. destfile = self.get_install_path()
  738. src = self.link
  739. if self.relative_trick:
  740. src = os.path.relpath(src, os.path.dirname(destfile))
  741. self.generator.bld.do_link(src, destfile, tsk=self)
  742. class InstallContext(BuildContext):
  743. '''installs the targets on the system'''
  744. cmd = 'install'
  745. def __init__(self, **kw):
  746. super(InstallContext, self).__init__(**kw)
  747. # list of targets to uninstall for removing the empty folders after uninstalling
  748. self.uninstall = []
  749. self.is_install = INSTALL
  750. def copy_fun(self, src, tgt, **kw):
  751. # override this if you want to strip executables
  752. # kw['tsk'].source is the task that created the files in the build
  753. if Utils.is_win32 and len(tgt) > 259 and not tgt.startswith('\\\\?\\'):
  754. tgt = '\\\\?\\' + tgt
  755. shutil.copy2(src, tgt)
  756. os.chmod(tgt, kw.get('chmod', Utils.O644))
  757. def do_install(self, src, tgt, **kw):
  758. """
  759. Copy a file from src to tgt with given file permissions. The actual copy is not performed
  760. if the source and target file have the same size and the same timestamps. When the copy occurs,
  761. the file is first removed and then copied (prevent stale inodes).
  762. This method is overridden in :py:meth:`waflib.Build.UninstallContext.do_install` to remove the file.
  763. :param src: file name as absolute path
  764. :type src: string
  765. :param tgt: file destination, as absolute path
  766. :type tgt: string
  767. :param chmod: installation mode
  768. :type chmod: int
  769. """
  770. d, _ = os.path.split(tgt)
  771. if not d:
  772. raise Errors.WafError('Invalid installation given %r->%r' % (src, tgt))
  773. Utils.check_dir(d)
  774. srclbl = src.replace(self.srcnode.abspath() + os.sep, '')
  775. if not Options.options.force:
  776. # check if the file is already there to avoid a copy
  777. try:
  778. st1 = os.stat(tgt)
  779. st2 = os.stat(src)
  780. except OSError:
  781. pass
  782. else:
  783. # same size and identical timestamps -> make no copy
  784. if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
  785. if not self.progress_bar:
  786. Logs.info('- install %s (from %s)' % (tgt, srclbl))
  787. return False
  788. if not self.progress_bar:
  789. Logs.info('+ install %s (from %s)' % (tgt, srclbl))
  790. # Give best attempt at making destination overwritable,
  791. # like the 'install' utility used by 'make install' does.
  792. try:
  793. os.chmod(tgt, Utils.O644 | stat.S_IMODE(os.stat(tgt).st_mode))
  794. except EnvironmentError:
  795. pass
  796. # following is for shared libs and stale inodes (-_-)
  797. try:
  798. os.remove(tgt)
  799. except OSError:
  800. pass
  801. try:
  802. self.copy_fun(src, tgt, **kw)
  803. except IOError:
  804. try:
  805. os.stat(src)
  806. except EnvironmentError:
  807. Logs.error('File %r does not exist' % src)
  808. raise Errors.WafError('Could not install the file %r' % tgt)
  809. def do_link(self, src, tgt, **kw):
  810. """
  811. Create a symlink from tgt to src.
  812. This method is overridden in :py:meth:`waflib.Build.UninstallContext.do_link` to remove the symlink.
  813. :param src: file name as absolute path
  814. :type src: string
  815. :param tgt: file destination, as absolute path
  816. :type tgt: string
  817. """
  818. d, _ = os.path.split(tgt)
  819. Utils.check_dir(d)
  820. link = False
  821. if not os.path.islink(tgt):
  822. link = True
  823. elif os.readlink(tgt) != src:
  824. link = True
  825. if link:
  826. try: os.remove(tgt)
  827. except OSError: pass
  828. if not self.progress_bar:
  829. Logs.info('+ symlink %s (to %s)' % (tgt, src))
  830. os.symlink(src, tgt)
  831. else:
  832. if not self.progress_bar:
  833. Logs.info('- symlink %s (to %s)' % (tgt, src))
  834. def run_task_now(self, tsk, postpone):
  835. """
  836. This method is called by :py:meth:`waflib.Build.InstallContext.install_files`,
  837. :py:meth:`waflib.Build.InstallContext.install_as` and :py:meth:`waflib.Build.InstallContext.symlink_as` immediately
  838. after the installation task is created. Its role is to force the immediate execution if necessary, that is when
  839. ``postpone=False`` was given.
  840. """
  841. tsk.post()
  842. if not postpone:
  843. if tsk.runnable_status() == Task.ASK_LATER:
  844. raise self.WafError('cannot post the task %r' % tsk)
  845. tsk.run()
  846. tsk.hasrun = True
  847. def install_files(self, dest, files, env=None, chmod=Utils.O644, relative_trick=False, cwd=None, add=True, postpone=True, task=None):
  848. """
  849. Create a task to install files on the system::
  850. def build(bld):
  851. bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
  852. :param dest: absolute path of the destination directory
  853. :type dest: string
  854. :param files: input files
  855. :type files: list of strings or list of nodes
  856. :param env: configuration set for performing substitutions in dest
  857. :type env: Configuration set
  858. :param relative_trick: preserve the folder hierarchy when installing whole folders
  859. :type relative_trick: bool
  860. :param cwd: parent node for searching srcfile, when srcfile is not a :py:class:`waflib.Node.Node`
  861. :type cwd: :py:class:`waflib.Node.Node`
  862. :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
  863. :type add: bool
  864. :param postpone: execute the task immediately to perform the installation
  865. :type postpone: bool
  866. """
  867. assert(dest)
  868. tsk = inst(env=env or self.env)
  869. tsk.bld = self
  870. tsk.path = cwd or self.path
  871. tsk.chmod = chmod
  872. tsk.task = task
  873. if isinstance(files, waflib.Node.Node):
  874. tsk.source = [files]
  875. else:
  876. tsk.source = Utils.to_list(files)
  877. tsk.dest = dest
  878. tsk.exec_task = tsk.exec_install_files
  879. tsk.relative_trick = relative_trick
  880. if add: self.add_to_group(tsk)
  881. self.run_task_now(tsk, postpone)
  882. return tsk
  883. def install_as(self, dest, srcfile, env=None, chmod=Utils.O644, cwd=None, add=True, postpone=True, task=None):
  884. """
  885. Create a task to install a file on the system with a different name::
  886. def build(bld):
  887. bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
  888. :param dest: absolute path of the destination file
  889. :type dest: string
  890. :param srcfile: input file
  891. :type srcfile: string or node
  892. :param cwd: parent node for searching srcfile, when srcfile is not a :py:class:`waflib.Node.Node`
  893. :type cwd: :py:class:`waflib.Node.Node`
  894. :param env: configuration set for performing substitutions in dest
  895. :type env: Configuration set
  896. :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
  897. :type add: bool
  898. :param postpone: execute the task immediately to perform the installation
  899. :type postpone: bool
  900. """
  901. assert(dest)
  902. tsk = inst(env=env or self.env)
  903. tsk.bld = self
  904. tsk.path = cwd or self.path
  905. tsk.chmod = chmod
  906. tsk.source = [srcfile]
  907. tsk.task = task
  908. tsk.dest = dest
  909. tsk.exec_task = tsk.exec_install_as
  910. if add: self.add_to_group(tsk)
  911. self.run_task_now(tsk, postpone)
  912. return tsk
  913. def symlink_as(self, dest, src, env=None, cwd=None, add=True, postpone=True, relative_trick=False, task=None):
  914. """
  915. Create a task to install a symlink::
  916. def build(bld):
  917. bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
  918. :param dest: absolute path of the symlink
  919. :type dest: string
  920. :param src: absolute or relative path of the link
  921. :type src: string
  922. :param env: configuration set for performing substitutions in dest
  923. :type env: Configuration set
  924. :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
  925. :type add: bool
  926. :param postpone: execute the task immediately to perform the installation
  927. :type postpone: bool
  928. :param relative_trick: make the symlink relative (default: ``False``)
  929. :type relative_trick: bool
  930. """
  931. if Utils.is_win32:
  932. # symlinks *cannot* work on that platform
  933. # TODO waf 1.9 - replace by install_as
  934. return
  935. assert(dest)
  936. tsk = inst(env=env or self.env)
  937. tsk.bld = self
  938. tsk.dest = dest
  939. tsk.path = cwd or self.path
  940. tsk.source = []
  941. tsk.task = task
  942. tsk.link = src
  943. tsk.relative_trick = relative_trick
  944. tsk.exec_task = tsk.exec_symlink_as
  945. if add: self.add_to_group(tsk)
  946. self.run_task_now(tsk, postpone)
  947. return tsk
  948. class UninstallContext(InstallContext):
  949. '''removes the targets installed'''
  950. cmd = 'uninstall'
  951. def __init__(self, **kw):
  952. super(UninstallContext, self).__init__(**kw)
  953. self.is_install = UNINSTALL
  954. def rm_empty_dirs(self, tgt):
  955. while tgt:
  956. tgt = os.path.dirname(tgt)
  957. try:
  958. os.rmdir(tgt)
  959. except OSError:
  960. break
  961. def do_install(self, src, tgt, **kw):
  962. """See :py:meth:`waflib.Build.InstallContext.do_install`"""
  963. if not self.progress_bar:
  964. Logs.info('- remove %s' % tgt)
  965. self.uninstall.append(tgt)
  966. try:
  967. os.remove(tgt)
  968. except OSError as e:
  969. if e.errno != errno.ENOENT:
  970. if not getattr(self, 'uninstall_error', None):
  971. self.uninstall_error = True
  972. Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)')
  973. if Logs.verbose > 1:
  974. Logs.warn('Could not remove %s (error code %r)' % (e.filename, e.errno))
  975. self.rm_empty_dirs(tgt)
  976. def do_link(self, src, tgt, **kw):
  977. """See :py:meth:`waflib.Build.InstallContext.do_link`"""
  978. try:
  979. if not self.progress_bar:
  980. Logs.info('- remove %s' % tgt)
  981. os.remove(tgt)
  982. except OSError:
  983. pass
  984. self.rm_empty_dirs(tgt)
  985. def execute(self):
  986. """
  987. See :py:func:`waflib.Context.Context.execute`
  988. """
  989. try:
  990. # do not execute any tasks
  991. def runnable_status(self):
  992. return Task.SKIP_ME
  993. setattr(Task.Task, 'runnable_status_back', Task.Task.runnable_status)
  994. setattr(Task.Task, 'runnable_status', runnable_status)
  995. super(UninstallContext, self).execute()
  996. finally:
  997. setattr(Task.Task, 'runnable_status', Task.Task.runnable_status_back)
  998. class CleanContext(BuildContext):
  999. '''cleans the project'''
  1000. cmd = 'clean'
  1001. def execute(self):
  1002. """
  1003. See :py:func:`waflib.Context.Context.execute`
  1004. """
  1005. self.restore()
  1006. if not self.all_envs:
  1007. self.load_envs()
  1008. self.recurse([self.run_dir])
  1009. try:
  1010. self.clean()
  1011. finally:
  1012. self.store()
  1013. def clean(self):
  1014. """Remove files from the build directory if possible, and reset the caches"""
  1015. Logs.debug('build: clean called')
  1016. if self.bldnode != self.srcnode:
  1017. # would lead to a disaster if top == out
  1018. lst=[]
  1019. for e in self.all_envs.values():
  1020. lst.extend(self.root.find_or_declare(f) for f in e[CFG_FILES])
  1021. for n in self.bldnode.ant_glob('**/*', excl='.lock* *conf_check_*/** config.log c4che/*', quiet=True):
  1022. if n in lst:
  1023. continue
  1024. n.delete()
  1025. self.root.children = {}
  1026. for v in 'node_deps task_sigs raw_deps'.split():
  1027. setattr(self, v, {})
  1028. class ListContext(BuildContext):
  1029. '''lists the targets to execute'''
  1030. cmd = 'list'
  1031. def execute(self):
  1032. """
  1033. See :py:func:`waflib.Context.Context.execute`.
  1034. """
  1035. self.restore()
  1036. if not self.all_envs:
  1037. self.load_envs()
  1038. self.recurse([self.run_dir])
  1039. self.pre_build()
  1040. # display the time elapsed in the progress bar
  1041. self.timer = Utils.Timer()
  1042. for g in self.groups:
  1043. for tg in g:
  1044. try:
  1045. f = tg.post
  1046. except AttributeError:
  1047. pass
  1048. else:
  1049. f()
  1050. try:
  1051. # force the cache initialization
  1052. self.get_tgen_by_name('')
  1053. except Exception:
  1054. pass
  1055. lst = list(self.task_gen_cache_names.keys())
  1056. lst.sort()
  1057. for k in lst:
  1058. Logs.pprint('GREEN', k)
  1059. class StepContext(BuildContext):
  1060. '''executes tasks in a step-by-step fashion, for debugging'''
  1061. cmd = 'step'
  1062. def __init__(self, **kw):
  1063. super(StepContext, self).__init__(**kw)
  1064. self.files = Options.options.files
  1065. def compile(self):
  1066. """
  1067. Compile the tasks matching the input/output files given (regular expression matching). Derived from :py:meth:`waflib.Build.BuildContext.compile`::
  1068. $ waf step --files=foo.c,bar.c,in:truc.c,out:bar.o
  1069. $ waf step --files=in:foo.cpp.1.o # link task only
  1070. """
  1071. if not self.files:
  1072. Logs.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
  1073. BuildContext.compile(self)
  1074. return
  1075. targets = None
  1076. if self.targets and self.targets != '*':
  1077. targets = self.targets.split(',')
  1078. for g in self.groups:
  1079. for tg in g:
  1080. if targets and tg.name not in targets:
  1081. continue
  1082. try:
  1083. f = tg.post
  1084. except AttributeError:
  1085. pass
  1086. else:
  1087. f()
  1088. for pat in self.files.split(','):
  1089. matcher = self.get_matcher(pat)
  1090. for tg in g:
  1091. if isinstance(tg, Task.TaskBase):
  1092. lst = [tg]
  1093. else:
  1094. lst = tg.tasks
  1095. for tsk in lst:
  1096. do_exec = False
  1097. for node in getattr(tsk, 'inputs', []):
  1098. if matcher(node, output=False):
  1099. do_exec = True
  1100. break
  1101. for node in getattr(tsk, 'outputs', []):
  1102. if matcher(node, output=True):
  1103. do_exec = True
  1104. break
  1105. if do_exec:
  1106. ret = tsk.run()
  1107. Logs.info('%s -> exit %r' % (str(tsk), ret))
  1108. def get_matcher(self, pat):
  1109. # this returns a function
  1110. inn = True
  1111. out = True
  1112. if pat.startswith('in:'):
  1113. out = False
  1114. pat = pat.replace('in:', '')
  1115. elif pat.startswith('out:'):
  1116. inn = False
  1117. pat = pat.replace('out:', '')
  1118. anode = self.root.find_node(pat)
  1119. pattern = None
  1120. if not anode:
  1121. if not pat.startswith('^'):
  1122. pat = '^.+?%s' % pat
  1123. if not pat.endswith('$'):
  1124. pat = '%s$' % pat
  1125. pattern = re.compile(pat)
  1126. def match(node, output):
  1127. if output == True and not out:
  1128. return False
  1129. if output == False and not inn:
  1130. return False
  1131. if anode:
  1132. return anode == node
  1133. else:
  1134. return pattern.match(node.abspath())
  1135. return match