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.

1350 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. tsk.hasrun = True
  851. def install_files(self, dest, files, env=None, chmod=Utils.O644, relative_trick=False, cwd=None, add=True, postpone=True, task=None):
  852. """
  853. Create a task to install files on the system::
  854. def build(bld):
  855. bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
  856. :param dest: absolute path of the destination directory
  857. :type dest: string
  858. :param files: input files
  859. :type files: list of strings or list of nodes
  860. :param env: configuration set for performing substitutions in dest
  861. :type env: Configuration set
  862. :param relative_trick: preserve the folder hierarchy when installing whole folders
  863. :type relative_trick: bool
  864. :param cwd: parent node for searching srcfile, when srcfile is not a :py:class:`waflib.Node.Node`
  865. :type cwd: :py:class:`waflib.Node.Node`
  866. :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
  867. :type add: bool
  868. :param postpone: execute the task immediately to perform the installation
  869. :type postpone: bool
  870. """
  871. tsk = inst(env=env or self.env)
  872. tsk.bld = self
  873. tsk.path = cwd or self.path
  874. tsk.chmod = chmod
  875. tsk.task = task
  876. if isinstance(files, waflib.Node.Node):
  877. tsk.source = [files]
  878. else:
  879. tsk.source = Utils.to_list(files)
  880. tsk.dest = dest
  881. tsk.exec_task = tsk.exec_install_files
  882. tsk.relative_trick = relative_trick
  883. if add: self.add_to_group(tsk)
  884. self.run_task_now(tsk, postpone)
  885. return tsk
  886. def install_as(self, dest, srcfile, env=None, chmod=Utils.O644, cwd=None, add=True, postpone=True, task=None):
  887. """
  888. Create a task to install a file on the system with a different name::
  889. def build(bld):
  890. bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
  891. :param dest: absolute path of the destination file
  892. :type dest: string
  893. :param srcfile: input file
  894. :type srcfile: string or node
  895. :param cwd: parent node for searching srcfile, when srcfile is not a :py:class:`waflib.Node.Node`
  896. :type cwd: :py:class:`waflib.Node.Node`
  897. :param env: configuration set for performing substitutions in dest
  898. :type env: Configuration set
  899. :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
  900. :type add: bool
  901. :param postpone: execute the task immediately to perform the installation
  902. :type postpone: bool
  903. """
  904. tsk = inst(env=env or self.env)
  905. tsk.bld = self
  906. tsk.path = cwd or self.path
  907. tsk.chmod = chmod
  908. tsk.source = [srcfile]
  909. tsk.task = task
  910. tsk.dest = dest
  911. tsk.exec_task = tsk.exec_install_as
  912. if add: self.add_to_group(tsk)
  913. self.run_task_now(tsk, postpone)
  914. return tsk
  915. def symlink_as(self, dest, src, env=None, cwd=None, add=True, postpone=True, relative_trick=False, task=None):
  916. """
  917. Create a task to install a symlink::
  918. def build(bld):
  919. bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
  920. :param dest: absolute path of the symlink
  921. :type dest: string
  922. :param src: absolute or relative path of the link
  923. :type src: string
  924. :param env: configuration set for performing substitutions in dest
  925. :type env: Configuration set
  926. :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
  927. :type add: bool
  928. :param postpone: execute the task immediately to perform the installation
  929. :type postpone: bool
  930. :param relative_trick: make the symlink relative (default: ``False``)
  931. :type relative_trick: bool
  932. """
  933. if Utils.is_win32:
  934. # symlinks *cannot* work on that platform
  935. return
  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