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