Do not assert/crash when bad clients send unordered events
[jack2.git] / waflib / Build.py
blob032e15f3ebe22fd6707c74891cf7c371a422a35d
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2010 (ita)
5 """
6 Classes related to the build phase (build, clean, install, step, etc)
8 The inheritance tree is the following:
10 """
12 import os, sys, errno, re, shutil, stat
13 try:
14 import cPickle
15 except ImportError:
16 import pickle as cPickle
17 from waflib import Runner, TaskGen, Utils, ConfigSet, Task, Logs, Options, Context, Errors
18 import waflib.Node
20 CACHE_DIR = 'c4che'
21 """Location of the cache files"""
23 CACHE_SUFFIX = '_cache.py'
24 """Suffix for the cache files"""
26 INSTALL = 1337
27 """Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
29 UNINSTALL = -1337
30 """Negative value '<-' uninstall, see :py:attr:`waflib.Build.BuildContext.is_install`"""
32 SAVED_ATTRS = 'root node_deps raw_deps task_sigs'.split()
33 """Build class members to save between the runs (root, node_deps, raw_deps, task_sigs)"""
35 CFG_FILES = 'cfg_files'
36 """Files from the build directory to hash before starting the build (``config.h`` written during the configuration)"""
38 POST_AT_ONCE = 0
39 """Post mode: all task generators are posted before the build really starts"""
41 POST_LAZY = 1
42 """Post mode: post the task generators group after group"""
44 POST_BOTH = 2
45 """Post mode: post the task generators at once, then re-check them for each group"""
47 PROTOCOL = -1
48 if sys.platform == 'cli':
49 PROTOCOL = 0
51 class BuildContext(Context.Context):
52 '''executes the build'''
54 cmd = 'build'
55 variant = ''
57 def __init__(self, **kw):
58 super(BuildContext, self).__init__(**kw)
60 self.is_install = 0
61 """Non-zero value when installing or uninstalling file"""
63 self.top_dir = kw.get('top_dir', Context.top_dir)
65 self.run_dir = kw.get('run_dir', Context.run_dir)
67 self.post_mode = POST_AT_ONCE
68 """post the task generators at once, group-by-group, or both"""
70 # output directory - may be set until the nodes are considered
71 self.out_dir = kw.get('out_dir', Context.out_dir)
73 self.cache_dir = kw.get('cache_dir', None)
74 if not self.cache_dir:
75 self.cache_dir = os.path.join(self.out_dir, CACHE_DIR)
77 # map names to environments, the '' must be defined
78 self.all_envs = {}
80 # ======================================= #
81 # cache variables
83 self.task_sigs = {}
84 """Signatures of the tasks (persists between build executions)"""
86 self.node_deps = {}
87 """Dict of node dependencies found by :py:meth:`waflib.Task.Task.scan` (persists between build executions)"""
89 self.raw_deps = {}
90 """Dict of custom data returned by :py:meth:`waflib.Task.Task.scan` (persists between build executions)"""
92 # list of folders that are already scanned
93 # so that we do not need to stat them one more time
94 self.cache_dir_contents = {}
96 self.task_gen_cache_names = {}
98 self.launch_dir = Context.launch_dir
100 self.jobs = Options.options.jobs
101 self.targets = Options.options.targets
102 self.keep = Options.options.keep
103 self.progress_bar = Options.options.progress_bar
105 ############ stuff below has not been reviewed
107 # Manual dependencies.
108 self.deps_man = Utils.defaultdict(list)
109 """Manual dependencies set by :py:meth:`waflib.Build.BuildContext.add_manual_dependency`"""
111 # just the structure here
112 self.current_group = 0
114 Current build group
117 self.groups = []
119 List containing lists of task generators
121 self.group_names = {}
123 Map group names to the group lists. See :py:meth:`waflib.Build.BuildContext.add_group`
126 def get_variant_dir(self):
127 """Getter for the variant_dir attribute"""
128 if not self.variant:
129 return self.out_dir
130 return os.path.join(self.out_dir, self.variant)
131 variant_dir = property(get_variant_dir, None)
133 def __call__(self, *k, **kw):
135 Create a task generator and add it to the current build group. The following forms are equivalent::
137 def build(bld):
138 tg = bld(a=1, b=2)
140 def build(bld):
141 tg = bld()
142 tg.a = 1
143 tg.b = 2
145 def build(bld):
146 tg = TaskGen.task_gen(a=1, b=2)
147 bld.add_to_group(tg, None)
149 :param group: group name to add the task generator to
150 :type group: string
152 kw['bld'] = self
153 ret = TaskGen.task_gen(*k, **kw)
154 self.task_gen_cache_names = {} # reset the cache, each time
155 self.add_to_group(ret, group=kw.get('group', None))
156 return ret
158 def rule(self, *k, **kw):
160 Wrapper for creating a task generator using the decorator notation. The following code::
162 @bld.rule(
163 target = "foo"
165 def _(tsk):
166 print("bar")
168 is equivalent to::
170 def bar(tsk):
171 print("bar")
173 bld(
174 target = "foo",
175 rule = bar,
178 def f(rule):
179 ret = self(*k, **kw)
180 ret.rule = rule
181 return ret
182 return f
184 def __copy__(self):
185 """Implemented to prevents copies of build contexts (raises an exception)"""
186 raise Errors.WafError('build contexts are not supposed to be copied')
188 def install_files(self, *k, **kw):
189 """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.install_files`"""
190 pass
192 def install_as(self, *k, **kw):
193 """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.install_as`"""
194 pass
196 def symlink_as(self, *k, **kw):
197 """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.symlink_as`"""
198 pass
200 def load_envs(self):
202 The configuration command creates files of the form ``build/c4che/NAMEcache.py``. This method
203 creates a :py:class:`waflib.ConfigSet.ConfigSet` instance for each ``NAME`` by reading those
204 files. The config sets are then stored in the dict :py:attr:`waflib.Build.BuildContext.allenvs`.
206 node = self.root.find_node(self.cache_dir)
207 if not node:
208 raise Errors.WafError('The project was not configured: run "waf configure" first!')
209 lst = node.ant_glob('**/*%s' % CACHE_SUFFIX, quiet=True)
211 if not lst:
212 raise Errors.WafError('The cache directory is empty: reconfigure the project')
214 for x in lst:
215 name = x.path_from(node).replace(CACHE_SUFFIX, '').replace('\\', '/')
216 env = ConfigSet.ConfigSet(x.abspath())
217 self.all_envs[name] = env
218 for f in env[CFG_FILES]:
219 newnode = self.root.find_resource(f)
220 try:
221 h = Utils.h_file(newnode.abspath())
222 except (IOError, AttributeError):
223 Logs.error('cannot find %r' % f)
224 h = Utils.SIG_NIL
225 newnode.sig = h
227 def init_dirs(self):
229 Initialize the project directory and the build directory by creating the nodes
230 :py:attr:`waflib.Build.BuildContext.srcnode` and :py:attr:`waflib.Build.BuildContext.bldnode`
231 corresponding to ``top_dir`` and ``variant_dir`` respectively. The ``bldnode`` directory will be
232 created if it does not exist.
235 if not (os.path.isabs(self.top_dir) and os.path.isabs(self.out_dir)):
236 raise Errors.WafError('The project was not configured: run "waf configure" first!')
238 self.path = self.srcnode = self.root.find_dir(self.top_dir)
239 self.bldnode = self.root.make_node(self.variant_dir)
240 self.bldnode.mkdir()
242 def execute(self):
244 Restore the data from previous builds and call :py:meth:`waflib.Build.BuildContext.execute_build`. Overrides from :py:func:`waflib.Context.Context.execute`
246 self.restore()
247 if not self.all_envs:
248 self.load_envs()
250 self.execute_build()
252 def execute_build(self):
254 Execute the build by:
256 * reading the scripts (see :py:meth:`waflib.Context.Context.recurse`)
257 * calling :py:meth:`waflib.Build.BuildContext.pre_build` to call user build functions
258 * calling :py:meth:`waflib.Build.BuildContext.compile` to process the tasks
259 * calling :py:meth:`waflib.Build.BuildContext.post_build` to call user build functions
262 Logs.info("Waf: Entering directory `%s'" % self.variant_dir)
263 self.recurse([self.run_dir])
264 self.pre_build()
266 # display the time elapsed in the progress bar
267 self.timer = Utils.Timer()
269 try:
270 self.compile()
271 finally:
272 if self.progress_bar == 1 and sys.stderr.isatty():
273 c = len(self.returned_tasks) or 1
274 m = self.progress_line(c, c, Logs.colors.BLUE, Logs.colors.NORMAL)
275 Logs.info(m, extra={'stream': sys.stderr, 'c1': Logs.colors.cursor_off, 'c2' : Logs.colors.cursor_on})
276 Logs.info("Waf: Leaving directory `%s'" % self.variant_dir)
277 self.post_build()
279 def restore(self):
281 Load the data from a previous run, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`
283 try:
284 env = ConfigSet.ConfigSet(os.path.join(self.cache_dir, 'build.config.py'))
285 except EnvironmentError:
286 pass
287 else:
288 if env['version'] < Context.HEXVERSION:
289 raise Errors.WafError('Version mismatch! reconfigure the project')
290 for t in env['tools']:
291 self.setup(**t)
293 dbfn = os.path.join(self.variant_dir, Context.DBFILE)
294 try:
295 data = Utils.readf(dbfn, 'rb')
296 except (IOError, EOFError):
297 # handle missing file/empty file
298 Logs.debug('build: Could not load the build cache %s (missing)' % dbfn)
299 else:
300 try:
301 waflib.Node.pickle_lock.acquire()
302 waflib.Node.Nod3 = self.node_class
303 try:
304 data = cPickle.loads(data)
305 except Exception as e:
306 Logs.debug('build: Could not pickle the build cache %s: %r' % (dbfn, e))
307 else:
308 for x in SAVED_ATTRS:
309 setattr(self, x, data[x])
310 finally:
311 waflib.Node.pickle_lock.release()
313 self.init_dirs()
315 def store(self):
317 Store the data for next runs, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`. Uses a temporary
318 file to avoid problems on ctrl+c.
321 data = {}
322 for x in SAVED_ATTRS:
323 data[x] = getattr(self, x)
324 db = os.path.join(self.variant_dir, Context.DBFILE)
326 try:
327 waflib.Node.pickle_lock.acquire()
328 waflib.Node.Nod3 = self.node_class
329 x = cPickle.dumps(data, PROTOCOL)
330 finally:
331 waflib.Node.pickle_lock.release()
333 Utils.writef(db + '.tmp', x, m='wb')
335 try:
336 st = os.stat(db)
337 os.remove(db)
338 if not Utils.is_win32: # win32 has no chown but we're paranoid
339 os.chown(db + '.tmp', st.st_uid, st.st_gid)
340 except (AttributeError, OSError):
341 pass
343 # do not use shutil.move (copy is not thread-safe)
344 os.rename(db + '.tmp', db)
346 def compile(self):
348 Run the build by creating an instance of :py:class:`waflib.Runner.Parallel`
349 The cache file is not written if the build is up to date (no task executed).
351 Logs.debug('build: compile()')
353 # use another object to perform the producer-consumer logic (reduce the complexity)
354 self.producer = Runner.Parallel(self, self.jobs)
355 self.producer.biter = self.get_build_iterator()
356 self.returned_tasks = [] # not part of the API yet
357 try:
358 self.producer.start()
359 except KeyboardInterrupt:
360 self.store()
361 raise
362 else:
363 if self.producer.dirty:
364 self.store()
366 if self.producer.error:
367 raise Errors.BuildError(self.producer.error)
369 def setup(self, tool, tooldir=None, funs=None):
371 Import waf tools, used to import those accessed during the configuration::
373 def configure(conf):
374 conf.load('glib2')
376 def build(bld):
377 pass # glib2 is imported implicitly
379 :param tool: tool list
380 :type tool: list
381 :param tooldir: optional tool directory (sys.path)
382 :type tooldir: list of string
383 :param funs: unused variable
385 if isinstance(tool, list):
386 for i in tool: self.setup(i, tooldir)
387 return
389 module = Context.load_tool(tool, tooldir)
390 if hasattr(module, "setup"): module.setup(self)
392 def get_env(self):
393 """Getter for the env property"""
394 try:
395 return self.all_envs[self.variant]
396 except KeyError:
397 return self.all_envs['']
398 def set_env(self, val):
399 """Setter for the env property"""
400 self.all_envs[self.variant] = val
402 env = property(get_env, set_env)
404 def add_manual_dependency(self, path, value):
406 Adds a dependency from a node object to a value::
408 def build(bld):
409 bld.add_manual_dependency(
410 bld.path.find_resource('wscript'),
411 bld.root.find_resource('/etc/fstab'))
413 :param path: file path
414 :type path: string or :py:class:`waflib.Node.Node`
415 :param value: value to depend on
416 :type value: :py:class:`waflib.Node.Node`, string, or function returning a string
418 if path is None:
419 raise ValueError('Invalid input')
421 if isinstance(path, waflib.Node.Node):
422 node = path
423 elif os.path.isabs(path):
424 node = self.root.find_resource(path)
425 else:
426 node = self.path.find_resource(path)
428 if isinstance(value, list):
429 self.deps_man[id(node)].extend(value)
430 else:
431 self.deps_man[id(node)].append(value)
433 def launch_node(self):
434 """Returns the launch directory as a :py:class:`waflib.Node.Node` object"""
435 try:
436 # private cache
437 return self.p_ln
438 except AttributeError:
439 self.p_ln = self.root.find_dir(self.launch_dir)
440 return self.p_ln
442 def hash_env_vars(self, env, vars_lst):
444 Hash configuration set variables::
446 def build(bld):
447 bld.hash_env_vars(bld.env, ['CXX', 'CC'])
449 :param env: Configuration Set
450 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
451 :param vars_lst: list of variables
452 :type vars_list: list of string
455 if not env.table:
456 env = env.parent
457 if not env:
458 return Utils.SIG_NIL
460 idx = str(id(env)) + str(vars_lst)
461 try:
462 cache = self.cache_env
463 except AttributeError:
464 cache = self.cache_env = {}
465 else:
466 try:
467 return self.cache_env[idx]
468 except KeyError:
469 pass
471 lst = [env[a] for a in vars_lst]
472 ret = Utils.h_list(lst)
473 Logs.debug('envhash: %s %r', Utils.to_hex(ret), lst)
475 cache[idx] = ret
477 return ret
479 def get_tgen_by_name(self, name):
481 Retrieves a task generator from its name or its target name
482 the name must be unique::
484 def build(bld):
485 tg = bld(name='foo')
486 tg == bld.get_tgen_by_name('foo')
488 cache = self.task_gen_cache_names
489 if not cache:
490 # create the index lazily
491 for g in self.groups:
492 for tg in g:
493 try:
494 cache[tg.name] = tg
495 except AttributeError:
496 # raised if not a task generator, which should be uncommon
497 pass
498 try:
499 return cache[name]
500 except KeyError:
501 raise Errors.WafError('Could not find a task generator for the name %r' % name)
503 def progress_line(self, state, total, col1, col2):
505 Compute the progress bar used by ``waf -p``
507 if not sys.stderr.isatty():
508 return ''
510 n = len(str(total))
512 Utils.rot_idx += 1
513 ind = Utils.rot_chr[Utils.rot_idx % 4]
515 pc = (100.*state)/total
516 eta = str(self.timer)
517 fs = "[%%%dd/%%%dd][%%s%%2d%%%%%%s][%s][" % (n, n, ind)
518 left = fs % (state, total, col1, pc, col2)
519 right = '][%s%s%s]' % (col1, eta, col2)
521 cols = Logs.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2)
522 if cols < 7: cols = 7
524 ratio = ((cols*state)//total) - 1
526 bar = ('='*ratio+'>').ljust(cols)
527 msg = Logs.indicator % (left, bar, right)
529 return msg
531 def declare_chain(self, *k, **kw):
533 Wrapper for :py:func:`waflib.TaskGen.declare_chain` provided for convenience
535 return TaskGen.declare_chain(*k, **kw)
537 def pre_build(self):
538 """Execute user-defined methods before the build starts, see :py:meth:`waflib.Build.BuildContext.add_pre_fun`"""
539 for m in getattr(self, 'pre_funs', []):
540 m(self)
542 def post_build(self):
543 """Executes the user-defined methods after the build is successful, see :py:meth:`waflib.Build.BuildContext.add_post_fun`"""
544 for m in getattr(self, 'post_funs', []):
545 m(self)
547 def add_pre_fun(self, meth):
549 Bind a method to execute after the scripts are read and before the build starts::
551 def mycallback(bld):
552 print("Hello, world!")
554 def build(bld):
555 bld.add_pre_fun(mycallback)
557 try:
558 self.pre_funs.append(meth)
559 except AttributeError:
560 self.pre_funs = [meth]
562 def add_post_fun(self, meth):
564 Bind a method to execute immediately after the build is successful::
566 def call_ldconfig(bld):
567 bld.exec_command('/sbin/ldconfig')
569 def build(bld):
570 if bld.cmd == 'install':
571 bld.add_pre_fun(call_ldconfig)
573 try:
574 self.post_funs.append(meth)
575 except AttributeError:
576 self.post_funs = [meth]
578 def get_group(self, x):
580 Get the group x, or return the current group if x is None
582 :param x: name or number or None
583 :type x: string, int or None
585 if not self.groups:
586 self.add_group()
587 if x is None:
588 return self.groups[self.current_group]
589 if x in self.group_names:
590 return self.group_names[x]
591 return self.groups[x]
593 def add_to_group(self, tgen, group=None):
594 """add a task or a task generator for the build"""
595 # paranoid
596 assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.TaskBase))
597 tgen.bld = self
598 self.get_group(group).append(tgen)
600 def get_group_name(self, g):
601 """name for the group g (utility)"""
602 if not isinstance(g, list):
603 g = self.groups[g]
604 for x in self.group_names:
605 if id(self.group_names[x]) == id(g):
606 return x
607 return ''
609 def get_group_idx(self, tg):
611 Index of the group containing the task generator given as argument::
613 def build(bld):
614 tg = bld(name='nada')
615 0 == bld.get_group_idx(tg)
617 :param tg: Task generator object
618 :type tg: :py:class:`waflib.TaskGen.task_gen`
620 se = id(tg)
621 for i in range(len(self.groups)):
622 for t in self.groups[i]:
623 if id(t) == se:
624 return i
625 return None
627 def add_group(self, name=None, move=True):
629 Add a new group of tasks/task generators. By default the new group becomes the default group for new task generators.
631 :param name: name for this group
632 :type name: string
633 :param move: set the group created as default group (True by default)
634 :type move: bool
636 #if self.groups and not self.groups[0].tasks:
637 # error('add_group: an empty group is already present')
638 if name and name in self.group_names:
639 Logs.error('add_group: name %s already present' % name)
640 g = []
641 self.group_names[name] = g
642 self.groups.append(g)
643 if move:
644 self.current_group = len(self.groups) - 1
646 def set_group(self, idx):
648 Set the current group to be idx: now new task generators will be added to this group by default::
650 def build(bld):
651 bld(rule='touch ${TGT}', target='foo.txt')
652 bld.add_group() # now the current group is 1
653 bld(rule='touch ${TGT}', target='bar.txt')
654 bld.set_group(0) # now the current group is 0
655 bld(rule='touch ${TGT}', target='truc.txt') # build truc.txt before bar.txt
657 :param idx: group name or group index
658 :type idx: string or int
660 if isinstance(idx, str):
661 g = self.group_names[idx]
662 for i in range(len(self.groups)):
663 if id(g) == id(self.groups[i]):
664 self.current_group = i
665 break
666 else:
667 self.current_group = idx
669 def total(self):
671 Approximate task count: this value may be inaccurate if task generators are posted lazily (see :py:attr:`waflib.Build.BuildContext.post_mode`).
672 The value :py:attr:`waflib.Runner.Parallel.total` is updated during the task execution.
674 total = 0
675 for group in self.groups:
676 for tg in group:
677 try:
678 total += len(tg.tasks)
679 except AttributeError:
680 total += 1
681 return total
683 def get_targets(self):
685 Return the task generator corresponding to the 'targets' list, used by :py:meth:`waflib.Build.BuildContext.get_build_iterator`::
687 $ waf --targets=myprogram,myshlib
689 to_post = []
690 min_grp = 0
691 for name in self.targets.split(','):
692 tg = self.get_tgen_by_name(name)
693 m = self.get_group_idx(tg)
694 if m > min_grp:
695 min_grp = m
696 to_post = [tg]
697 elif m == min_grp:
698 to_post.append(tg)
699 return (min_grp, to_post)
701 def get_all_task_gen(self):
703 Utility method, returns a list of all task generators - if you need something more complicated, implement your own
705 lst = []
706 for g in self.groups:
707 lst.extend(g)
708 return lst
710 def post_group(self):
712 Post the task generators from the group indexed by self.cur, used by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
714 if self.targets == '*':
715 for tg in self.groups[self.cur]:
716 try:
717 f = tg.post
718 except AttributeError:
719 pass
720 else:
722 elif self.targets:
723 if self.cur < self._min_grp:
724 for tg in self.groups[self.cur]:
725 try:
726 f = tg.post
727 except AttributeError:
728 pass
729 else:
731 else:
732 for tg in self._exact_tg:
733 tg.post()
734 else:
735 ln = self.launch_node()
736 if ln.is_child_of(self.bldnode):
737 Logs.warn('Building from the build directory, forcing --targets=*')
738 ln = self.srcnode
739 elif not ln.is_child_of(self.srcnode):
740 Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)' % (ln.abspath(), self.srcnode.abspath()))
741 ln = self.srcnode
742 for tg in self.groups[self.cur]:
743 try:
744 f = tg.post
745 except AttributeError:
746 pass
747 else:
748 if tg.path.is_child_of(ln):
751 def get_tasks_group(self, idx):
753 Return all the tasks for the group of num idx, used by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
755 tasks = []
756 for tg in self.groups[idx]:
757 try:
758 tasks.extend(tg.tasks)
759 except AttributeError: # not a task generator, can be the case for installation tasks
760 tasks.append(tg)
761 return tasks
763 def get_build_iterator(self):
765 Creates a generator object that returns lists of tasks executable in parallel (yield)
767 :return: tasks which can be executed immediatly
768 :rtype: list of :py:class:`waflib.Task.TaskBase`
770 self.cur = 0
772 if self.targets and self.targets != '*':
773 (self._min_grp, self._exact_tg) = self.get_targets()
775 global lazy_post
776 if self.post_mode != POST_LAZY:
777 while self.cur < len(self.groups):
778 self.post_group()
779 self.cur += 1
780 self.cur = 0
782 while self.cur < len(self.groups):
783 # first post the task generators for the group
784 if self.post_mode != POST_AT_ONCE:
785 self.post_group()
787 # then extract the tasks
788 tasks = self.get_tasks_group(self.cur)
789 # if the constraints are set properly (ext_in/ext_out, before/after)
790 # the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
791 # (but leave set_file_constraints for the installation step)
793 # if the tasks have only files, set_file_constraints is required but set_precedence_constraints is not necessary
795 Task.set_file_constraints(tasks)
796 Task.set_precedence_constraints(tasks)
798 self.cur_tasks = tasks
799 self.cur += 1
800 if not tasks: # return something else the build will stop
801 continue
802 yield tasks
803 while 1:
804 yield []
806 class inst(Task.Task):
808 Special task used for installing files and symlinks, it behaves both like a task
809 and like a task generator
811 color = 'CYAN'
813 def uid(self):
814 lst = [self.dest, self.path] + self.source
815 return Utils.h_list(repr(lst))
817 def post(self):
819 Same interface as in :py:meth:`waflib.TaskGen.task_gen.post`
821 buf = []
822 for x in self.source:
823 if isinstance(x, waflib.Node.Node):
824 y = x
825 else:
826 y = self.path.find_resource(x)
827 if not y:
828 if os.path.isabs(x):
829 y = self.bld.root.make_node(x)
830 else:
831 y = self.path.make_node(x)
832 buf.append(y)
833 self.inputs = buf
835 def runnable_status(self):
837 Installation tasks are always executed, so this method returns either :py:const:`waflib.Task.ASK_LATER` or :py:const:`waflib.Task.RUN_ME`.
839 ret = super(inst, self).runnable_status()
840 if ret == Task.SKIP_ME:
841 return Task.RUN_ME
842 return ret
844 def __str__(self):
845 """Return an empty string to disable the display"""
846 return ''
848 def run(self):
849 """The attribute 'exec_task' holds the method to execute"""
850 return self.generator.exec_task()
852 def get_install_path(self, destdir=True):
854 Installation path obtained from ``self.dest`` and prefixed by the destdir.
855 The variables such as '${PREFIX}/bin' are substituted.
857 dest = Utils.subst_vars(self.dest, self.env)
858 dest = dest.replace('/', os.sep)
859 if destdir and Options.options.destdir:
860 dest = os.path.join(Options.options.destdir, os.path.splitdrive(dest)[1].lstrip(os.sep))
861 return dest
863 def exec_install_files(self):
865 Predefined method for installing files
867 destpath = self.get_install_path()
868 if not destpath:
869 raise Errors.WafError('unknown installation path %r' % self.generator)
870 for x, y in zip(self.source, self.inputs):
871 if self.relative_trick:
872 destfile = os.path.join(destpath, y.path_from(self.path))
873 else:
874 destfile = os.path.join(destpath, y.name)
875 self.generator.bld.do_install(y.abspath(), destfile, chmod=self.chmod, tsk=self)
877 def exec_install_as(self):
879 Predefined method for installing one file with a given name
881 destfile = self.get_install_path()
882 self.generator.bld.do_install(self.inputs[0].abspath(), destfile, chmod=self.chmod, tsk=self)
884 def exec_symlink_as(self):
886 Predefined method for installing a symlink
888 destfile = self.get_install_path()
889 src = self.link
890 if self.relative_trick:
891 src = os.path.relpath(src, os.path.dirname(destfile))
892 self.generator.bld.do_link(src, destfile, tsk=self)
894 class InstallContext(BuildContext):
895 '''installs the targets on the system'''
896 cmd = 'install'
898 def __init__(self, **kw):
899 super(InstallContext, self).__init__(**kw)
901 # list of targets to uninstall for removing the empty folders after uninstalling
902 self.uninstall = []
903 self.is_install = INSTALL
905 def copy_fun(self, src, tgt, **kw):
906 # override this if you want to strip executables
907 # kw['tsk'].source is the task that created the files in the build
908 if Utils.is_win32 and len(tgt) > 259 and not tgt.startswith('\\\\?\\'):
909 tgt = '\\\\?\\' + tgt
910 shutil.copy2(src, tgt)
911 os.chmod(tgt, kw.get('chmod', Utils.O644))
913 def do_install(self, src, tgt, **kw):
915 Copy a file from src to tgt with given file permissions. The actual copy is not performed
916 if the source and target file have the same size and the same timestamps. When the copy occurs,
917 the file is first removed and then copied (prevent stale inodes).
919 This method is overridden in :py:meth:`waflib.Build.UninstallContext.do_install` to remove the file.
921 :param src: file name as absolute path
922 :type src: string
923 :param tgt: file destination, as absolute path
924 :type tgt: string
925 :param chmod: installation mode
926 :type chmod: int
928 d, _ = os.path.split(tgt)
929 if not d:
930 raise Errors.WafError('Invalid installation given %r->%r' % (src, tgt))
931 Utils.check_dir(d)
933 srclbl = src.replace(self.srcnode.abspath() + os.sep, '')
934 if not Options.options.force:
935 # check if the file is already there to avoid a copy
936 try:
937 st1 = os.stat(tgt)
938 st2 = os.stat(src)
939 except OSError:
940 pass
941 else:
942 # same size and identical timestamps -> make no copy
943 if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
944 if not self.progress_bar:
945 Logs.info('- install %s (from %s)' % (tgt, srclbl))
946 return False
948 if not self.progress_bar:
949 Logs.info('+ install %s (from %s)' % (tgt, srclbl))
951 # Give best attempt at making destination overwritable,
952 # like the 'install' utility used by 'make install' does.
953 try:
954 os.chmod(tgt, Utils.O644 | stat.S_IMODE(os.stat(tgt).st_mode))
955 except EnvironmentError:
956 pass
958 # following is for shared libs and stale inodes (-_-)
959 try:
960 os.remove(tgt)
961 except OSError:
962 pass
964 try:
965 self.copy_fun(src, tgt, **kw)
966 except IOError:
967 try:
968 os.stat(src)
969 except EnvironmentError:
970 Logs.error('File %r does not exist' % src)
971 raise Errors.WafError('Could not install the file %r' % tgt)
973 def do_link(self, src, tgt, **kw):
975 Create a symlink from tgt to src.
977 This method is overridden in :py:meth:`waflib.Build.UninstallContext.do_link` to remove the symlink.
979 :param src: file name as absolute path
980 :type src: string
981 :param tgt: file destination, as absolute path
982 :type tgt: string
984 d, _ = os.path.split(tgt)
985 Utils.check_dir(d)
987 link = False
988 if not os.path.islink(tgt):
989 link = True
990 elif os.readlink(tgt) != src:
991 link = True
993 if link:
994 try: os.remove(tgt)
995 except OSError: pass
996 if not self.progress_bar:
997 Logs.info('+ symlink %s (to %s)' % (tgt, src))
998 os.symlink(src, tgt)
999 else:
1000 if not self.progress_bar:
1001 Logs.info('- symlink %s (to %s)' % (tgt, src))
1003 def run_task_now(self, tsk, postpone):
1005 This method is called by :py:meth:`waflib.Build.InstallContext.install_files`,
1006 :py:meth:`waflib.Build.InstallContext.install_as` and :py:meth:`waflib.Build.InstallContext.symlink_as` immediately
1007 after the installation task is created. Its role is to force the immediate execution if necessary, that is when
1008 ``postpone=False`` was given.
1010 tsk.post()
1011 if not postpone:
1012 if tsk.runnable_status() == Task.ASK_LATER:
1013 raise self.WafError('cannot post the task %r' % tsk)
1014 tsk.run()
1015 tsk.hasrun = True
1017 def install_files(self, dest, files, env=None, chmod=Utils.O644, relative_trick=False, cwd=None, add=True, postpone=True, task=None):
1019 Create a task to install files on the system::
1021 def build(bld):
1022 bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
1024 :param dest: absolute path of the destination directory
1025 :type dest: string
1026 :param files: input files
1027 :type files: list of strings or list of nodes
1028 :param env: configuration set for performing substitutions in dest
1029 :type env: Configuration set
1030 :param relative_trick: preserve the folder hierarchy when installing whole folders
1031 :type relative_trick: bool
1032 :param cwd: parent node for searching srcfile, when srcfile is not a :py:class:`waflib.Node.Node`
1033 :type cwd: :py:class:`waflib.Node.Node`
1034 :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
1035 :type add: bool
1036 :param postpone: execute the task immediately to perform the installation
1037 :type postpone: bool
1039 assert(dest)
1040 tsk = inst(env=env or self.env)
1041 tsk.bld = self
1042 tsk.path = cwd or self.path
1043 tsk.chmod = chmod
1044 tsk.task = task
1045 if isinstance(files, waflib.Node.Node):
1046 tsk.source = [files]
1047 else:
1048 tsk.source = Utils.to_list(files)
1049 tsk.dest = dest
1050 tsk.exec_task = tsk.exec_install_files
1051 tsk.relative_trick = relative_trick
1052 if add: self.add_to_group(tsk)
1053 self.run_task_now(tsk, postpone)
1054 return tsk
1056 def install_as(self, dest, srcfile, env=None, chmod=Utils.O644, cwd=None, add=True, postpone=True, task=None):
1058 Create a task to install a file on the system with a different name::
1060 def build(bld):
1061 bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
1063 :param dest: absolute path of the destination file
1064 :type dest: string
1065 :param srcfile: input file
1066 :type srcfile: string or node
1067 :param cwd: parent node for searching srcfile, when srcfile is not a :py:class:`waflib.Node.Node`
1068 :type cwd: :py:class:`waflib.Node.Node`
1069 :param env: configuration set for performing substitutions in dest
1070 :type env: Configuration set
1071 :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
1072 :type add: bool
1073 :param postpone: execute the task immediately to perform the installation
1074 :type postpone: bool
1076 assert(dest)
1077 tsk = inst(env=env or self.env)
1078 tsk.bld = self
1079 tsk.path = cwd or self.path
1080 tsk.chmod = chmod
1081 tsk.source = [srcfile]
1082 tsk.task = task
1083 tsk.dest = dest
1084 tsk.exec_task = tsk.exec_install_as
1085 if add: self.add_to_group(tsk)
1086 self.run_task_now(tsk, postpone)
1087 return tsk
1089 def symlink_as(self, dest, src, env=None, cwd=None, add=True, postpone=True, relative_trick=False, task=None):
1091 Create a task to install a symlink::
1093 def build(bld):
1094 bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
1096 :param dest: absolute path of the symlink
1097 :type dest: string
1098 :param src: absolute or relative path of the link
1099 :type src: string
1100 :param env: configuration set for performing substitutions in dest
1101 :type env: Configuration set
1102 :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
1103 :type add: bool
1104 :param postpone: execute the task immediately to perform the installation
1105 :type postpone: bool
1106 :param relative_trick: make the symlink relative (default: ``False``)
1107 :type relative_trick: bool
1109 if Utils.is_win32:
1110 # symlinks *cannot* work on that platform
1111 # TODO waf 1.9 - replace by install_as
1112 return
1113 assert(dest)
1114 tsk = inst(env=env or self.env)
1115 tsk.bld = self
1116 tsk.dest = dest
1117 tsk.path = cwd or self.path
1118 tsk.source = []
1119 tsk.task = task
1120 tsk.link = src
1121 tsk.relative_trick = relative_trick
1122 tsk.exec_task = tsk.exec_symlink_as
1123 if add: self.add_to_group(tsk)
1124 self.run_task_now(tsk, postpone)
1125 return tsk
1127 class UninstallContext(InstallContext):
1128 '''removes the targets installed'''
1129 cmd = 'uninstall'
1131 def __init__(self, **kw):
1132 super(UninstallContext, self).__init__(**kw)
1133 self.is_install = UNINSTALL
1135 def rm_empty_dirs(self, tgt):
1136 while tgt:
1137 tgt = os.path.dirname(tgt)
1138 try:
1139 os.rmdir(tgt)
1140 except OSError:
1141 break
1143 def do_install(self, src, tgt, **kw):
1144 """See :py:meth:`waflib.Build.InstallContext.do_install`"""
1145 if not self.progress_bar:
1146 Logs.info('- remove %s' % tgt)
1148 self.uninstall.append(tgt)
1149 try:
1150 os.remove(tgt)
1151 except OSError as e:
1152 if e.errno != errno.ENOENT:
1153 if not getattr(self, 'uninstall_error', None):
1154 self.uninstall_error = True
1155 Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)')
1156 if Logs.verbose > 1:
1157 Logs.warn('Could not remove %s (error code %r)' % (e.filename, e.errno))
1159 self.rm_empty_dirs(tgt)
1161 def do_link(self, src, tgt, **kw):
1162 """See :py:meth:`waflib.Build.InstallContext.do_link`"""
1163 try:
1164 if not self.progress_bar:
1165 Logs.info('- remove %s' % tgt)
1166 os.remove(tgt)
1167 except OSError:
1168 pass
1170 self.rm_empty_dirs(tgt)
1172 def execute(self):
1174 See :py:func:`waflib.Context.Context.execute`
1176 try:
1177 # do not execute any tasks
1178 def runnable_status(self):
1179 return Task.SKIP_ME
1180 setattr(Task.Task, 'runnable_status_back', Task.Task.runnable_status)
1181 setattr(Task.Task, 'runnable_status', runnable_status)
1183 super(UninstallContext, self).execute()
1184 finally:
1185 setattr(Task.Task, 'runnable_status', Task.Task.runnable_status_back)
1187 class CleanContext(BuildContext):
1188 '''cleans the project'''
1189 cmd = 'clean'
1190 def execute(self):
1192 See :py:func:`waflib.Context.Context.execute`
1194 self.restore()
1195 if not self.all_envs:
1196 self.load_envs()
1198 self.recurse([self.run_dir])
1199 try:
1200 self.clean()
1201 finally:
1202 self.store()
1204 def clean(self):
1205 """Remove files from the build directory if possible, and reset the caches"""
1206 Logs.debug('build: clean called')
1208 if self.bldnode != self.srcnode:
1209 # would lead to a disaster if top == out
1210 lst=[]
1211 for e in self.all_envs.values():
1212 lst.extend(self.root.find_or_declare(f) for f in e[CFG_FILES])
1213 for n in self.bldnode.ant_glob('**/*', excl='.lock* *conf_check_*/** config.log c4che/*', quiet=True):
1214 if n in lst:
1215 continue
1216 n.delete()
1217 self.root.children = {}
1219 for v in 'node_deps task_sigs raw_deps'.split():
1220 setattr(self, v, {})
1222 class ListContext(BuildContext):
1223 '''lists the targets to execute'''
1225 cmd = 'list'
1226 def execute(self):
1228 See :py:func:`waflib.Context.Context.execute`.
1230 self.restore()
1231 if not self.all_envs:
1232 self.load_envs()
1234 self.recurse([self.run_dir])
1235 self.pre_build()
1237 # display the time elapsed in the progress bar
1238 self.timer = Utils.Timer()
1240 for g in self.groups:
1241 for tg in g:
1242 try:
1243 f = tg.post
1244 except AttributeError:
1245 pass
1246 else:
1249 try:
1250 # force the cache initialization
1251 self.get_tgen_by_name('')
1252 except Exception:
1253 pass
1254 lst = list(self.task_gen_cache_names.keys())
1255 lst.sort()
1256 for k in lst:
1257 Logs.pprint('GREEN', k)
1259 class StepContext(BuildContext):
1260 '''executes tasks in a step-by-step fashion, for debugging'''
1261 cmd = 'step'
1263 def __init__(self, **kw):
1264 super(StepContext, self).__init__(**kw)
1265 self.files = Options.options.files
1267 def compile(self):
1269 Compile the tasks matching the input/output files given (regular expression matching). Derived from :py:meth:`waflib.Build.BuildContext.compile`::
1271 $ waf step --files=foo.c,bar.c,in:truc.c,out:bar.o
1272 $ waf step --files=in:foo.cpp.1.o # link task only
1275 if not self.files:
1276 Logs.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
1277 BuildContext.compile(self)
1278 return
1280 targets = None
1281 if self.targets and self.targets != '*':
1282 targets = self.targets.split(',')
1284 for g in self.groups:
1285 for tg in g:
1286 if targets and tg.name not in targets:
1287 continue
1289 try:
1290 f = tg.post
1291 except AttributeError:
1292 pass
1293 else:
1296 for pat in self.files.split(','):
1297 matcher = self.get_matcher(pat)
1298 for tg in g:
1299 if isinstance(tg, Task.TaskBase):
1300 lst = [tg]
1301 else:
1302 lst = tg.tasks
1303 for tsk in lst:
1304 do_exec = False
1305 for node in getattr(tsk, 'inputs', []):
1306 if matcher(node, output=False):
1307 do_exec = True
1308 break
1309 for node in getattr(tsk, 'outputs', []):
1310 if matcher(node, output=True):
1311 do_exec = True
1312 break
1313 if do_exec:
1314 ret = tsk.run()
1315 Logs.info('%s -> exit %r' % (str(tsk), ret))
1317 def get_matcher(self, pat):
1318 # this returns a function
1319 inn = True
1320 out = True
1321 if pat.startswith('in:'):
1322 out = False
1323 pat = pat.replace('in:', '')
1324 elif pat.startswith('out:'):
1325 inn = False
1326 pat = pat.replace('out:', '')
1328 anode = self.root.find_node(pat)
1329 pattern = None
1330 if not anode:
1331 if not pat.startswith('^'):
1332 pat = '^.+?%s' % pat
1333 if not pat.endswith('$'):
1334 pat = '%s$' % pat
1335 pattern = re.compile(pat)
1337 def match(node, output):
1338 if output == True and not out:
1339 return False
1340 if output == False and not inn:
1341 return False
1343 if anode:
1344 return anode == node
1345 else:
1346 return pattern.match(node.abspath())
1347 return match