3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
7 # Thomas Nagy, 2005-2018 (ita)
10 Classes related to the build phase (build, clean, install, step, etc)
12 The inheritance tree is the following:
16 import os, sys, errno, re, shutil, stat
20 import pickle as cPickle
21 from waflib import Node, Runner, TaskGen, Utils, ConfigSet, Task, Logs, Options, Context, Errors
24 """Name of the cache directory"""
26 CACHE_SUFFIX = '_cache.py'
27 """ConfigSet cache files for variants are written under :py:attr:´waflib.Build.CACHE_DIR´ in the form ´variant_name´_cache.py"""
30 """Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
33 """Negative value '<-' uninstall, see :py:attr:`waflib.Build.BuildContext.is_install`"""
35 SAVED_ATTRS = 'root node_sigs task_sigs imp_sigs raw_deps node_deps'.split()
36 """Build class members to save between the runs; these should be all dicts
37 except for `root` which represents a :py:class:`waflib.Node.Node` instance
40 CFG_FILES = 'cfg_files'
41 """Files from the build directory to hash before starting the build (``config.h`` written during the configuration)"""
44 """Post mode: all task generators are posted before any task executed"""
47 """Post mode: post the task generators group after group, the tasks in the next group are created when the tasks in the previous groups are done"""
50 if sys.platform == 'cli':
53 class BuildContext(Context.Context):
54 '''executes the build'''
59 def __init__(self, **kw):
60 super(BuildContext, self).__init__(**kw)
63 """Non-zero value when installing or uninstalling file"""
65 self.top_dir = kw.get('top_dir', Context.top_dir)
66 """See :py:attr:`waflib.Context.top_dir`; prefer :py:attr:`waflib.Build.BuildContext.srcnode`"""
68 self.out_dir = kw.get('out_dir', Context.out_dir)
69 """See :py:attr:`waflib.Context.out_dir`; prefer :py:attr:`waflib.Build.BuildContext.bldnode`"""
71 self.run_dir = kw.get('run_dir', Context.run_dir)
72 """See :py:attr:`waflib.Context.run_dir`"""
74 self.launch_dir = Context.launch_dir
75 """See :py:attr:`waflib.Context.out_dir`; prefer :py:meth:`waflib.Build.BuildContext.launch_node`"""
77 self.post_mode = POST_LAZY
78 """Whether to post the task generators at once or group-by-group (default is group-by-group)"""
80 self.cache_dir = kw.get('cache_dir')
81 if not self.cache_dir:
82 self.cache_dir = os.path.join(self.out_dir, CACHE_DIR)
85 """Map names to :py:class:`waflib.ConfigSet.ConfigSet`, the empty string must map to the default environment"""
87 # ======================================= #
91 """Dict mapping build nodes to task identifier (uid), it indicates whether a task created a particular file (persists across builds)"""
94 """Dict mapping task identifiers (uid) to task signatures (persists across builds)"""
97 """Dict mapping task identifiers (uid) to implicit task dependencies used for scanning targets (persists across builds)"""
100 """Dict mapping task identifiers (uid) to node dependencies found by :py:meth:`waflib.Task.Task.scan` (persists across builds)"""
103 """Dict mapping task identifiers (uid) to custom data returned by :py:meth:`waflib.Task.Task.scan` (persists across builds)"""
105 self.task_gen_cache_names = {}
107 self.jobs = Options.options.jobs
108 """Amount of jobs to run in parallel"""
110 self.targets = Options.options.targets
111 """List of targets to build (default: \*)"""
113 self.keep = Options.options.keep
114 """Whether the build should continue past errors"""
116 self.progress_bar = Options.options.progress_bar
118 Level of progress status:
126 # Manual dependencies.
127 self.deps_man = Utils.defaultdict(list)
128 """Manual dependencies set by :py:meth:`waflib.Build.BuildContext.add_manual_dependency`"""
130 # just the structure here
131 self.current_group = 0
138 List containing lists of task generators
141 self.group_names = {}
143 Map group names to the group lists. See :py:meth:`waflib.Build.BuildContext.add_group`
146 for v in SAVED_ATTRS:
147 if not hasattr(self, v):
150 def get_variant_dir(self):
151 """Getter for the variant_dir attribute"""
154 return os.path.join(self.out_dir, os.path.normpath(self.variant))
155 variant_dir = property(get_variant_dir, None)
157 def __call__(self, *k, **kw):
159 Create a task generator and add it to the current build group. The following forms are equivalent::
170 tg = TaskGen.task_gen(a=1, b=2)
171 bld.add_to_group(tg, None)
173 :param group: group name to add the task generator to
177 ret = TaskGen.task_gen(*k, **kw)
178 self.task_gen_cache_names = {} # reset the cache, each time
179 self.add_to_group(ret, group=kw.get('group'))
184 Build contexts cannot be copied
186 :raises: :py:class:`waflib.Errors.WafError`
188 raise Errors.WafError('build contexts cannot be copied')
192 The configuration command creates files of the form ``build/c4che/NAMEcache.py``. This method
193 creates a :py:class:`waflib.ConfigSet.ConfigSet` instance for each ``NAME`` by reading those
194 files and stores them in :py:attr:`waflib.Build.BuildContext.allenvs`.
196 node = self.root.find_node(self.cache_dir)
198 raise Errors.WafError('The project was not configured: run "waf configure" first!')
199 lst = node.ant_glob('**/*%s' % CACHE_SUFFIX, quiet=True)
202 raise Errors.WafError('The cache directory is empty: reconfigure the project')
205 name = x.path_from(node).replace(CACHE_SUFFIX, '').replace('\\', '/')
206 env = ConfigSet.ConfigSet(x.abspath())
207 self.all_envs[name] = env
208 for f in env[CFG_FILES]:
209 newnode = self.root.find_resource(f)
210 if not newnode or not newnode.exists():
211 raise Errors.WafError('Missing configuration file %r, reconfigure the project!' % f)
215 Initialize the project directory and the build directory by creating the nodes
216 :py:attr:`waflib.Build.BuildContext.srcnode` and :py:attr:`waflib.Build.BuildContext.bldnode`
217 corresponding to ``top_dir`` and ``variant_dir`` respectively. The ``bldnode`` directory is
218 created if necessary.
220 if not (os.path.isabs(self.top_dir) and os.path.isabs(self.out_dir)):
221 raise Errors.WafError('The project was not configured: run "waf configure" first!')
223 self.path = self.srcnode = self.root.find_dir(self.top_dir)
224 self.bldnode = self.root.make_node(self.variant_dir)
229 Restore data from previous builds and call :py:meth:`waflib.Build.BuildContext.execute_build`.
230 Overrides from :py:func:`waflib.Context.Context.execute`
233 if not self.all_envs:
237 def execute_build(self):
239 Execute the build by:
241 * reading the scripts (see :py:meth:`waflib.Context.Context.recurse`)
242 * calling :py:meth:`waflib.Build.BuildContext.pre_build` to call user build functions
243 * calling :py:meth:`waflib.Build.BuildContext.compile` to process the tasks
244 * calling :py:meth:`waflib.Build.BuildContext.post_build` to call user build functions
247 Logs.info("Waf: Entering directory `%s'", self.variant_dir)
248 self.recurse([self.run_dir])
251 # display the time elapsed in the progress bar
252 self.timer = Utils.Timer()
257 if self.progress_bar == 1 and sys.stderr.isatty():
258 c = self.producer.processed or 1
259 m = self.progress_line(c, c, Logs.colors.BLUE, Logs.colors.NORMAL)
260 Logs.info(m, extra={'stream': sys.stderr, 'c1': Logs.colors.cursor_off, 'c2' : Logs.colors.cursor_on})
261 Logs.info("Waf: Leaving directory `%s'", self.variant_dir)
263 self.producer.bld = None
265 except AttributeError:
271 Load data from a previous run, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`
274 env = ConfigSet.ConfigSet(os.path.join(self.cache_dir, 'build.config.py'))
275 except EnvironmentError:
278 if env.version < Context.HEXVERSION:
279 raise Errors.WafError('Project was configured with a different version of Waf, please reconfigure it')
284 dbfn = os.path.join(self.variant_dir, Context.DBFILE)
286 data = Utils.readf(dbfn, 'rb')
287 except (EnvironmentError, EOFError):
288 # handle missing file/empty file
289 Logs.debug('build: Could not load the build cache %s (missing)', dbfn)
292 Node.pickle_lock.acquire()
293 Node.Nod3 = self.node_class
295 data = cPickle.loads(data)
296 except Exception as e:
297 Logs.debug('build: Could not pickle the build cache %s: %r', dbfn, e)
299 for x in SAVED_ATTRS:
300 setattr(self, x, data.get(x, {}))
302 Node.pickle_lock.release()
308 Store data for next runs, set the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`. Uses a temporary
309 file to avoid problems on ctrl+c.
312 for x in SAVED_ATTRS:
313 data[x] = getattr(self, x)
314 db = os.path.join(self.variant_dir, Context.DBFILE)
317 Node.pickle_lock.acquire()
318 Node.Nod3 = self.node_class
319 x = cPickle.dumps(data, PROTOCOL)
321 Node.pickle_lock.release()
323 Utils.writef(db + '.tmp', x, m='wb')
328 if not Utils.is_win32: # win32 has no chown but we're paranoid
329 os.chown(db + '.tmp', st.st_uid, st.st_gid)
330 except (AttributeError, OSError):
333 # do not use shutil.move (copy is not thread-safe)
334 os.rename(db + '.tmp', db)
338 Run the build by creating an instance of :py:class:`waflib.Runner.Parallel`
339 The cache file is written when at least a task was executed.
341 :raises: :py:class:`waflib.Errors.BuildError` in case the build fails
343 Logs.debug('build: compile()')
345 # delegate the producer-consumer logic to another object to reduce the complexity
346 self.producer = Runner.Parallel(self, self.jobs)
347 self.producer.biter = self.get_build_iterator()
349 self.producer.start()
350 except KeyboardInterrupt:
358 if self.producer.error:
359 raise Errors.BuildError(self.producer.error)
362 return self.producer.dirty
364 def setup(self, tool, tooldir=None, funs=None):
366 Import waf tools defined during the configuration::
372 pass # glib2 is imported implicitly
374 :param tool: tool list
376 :param tooldir: optional tool directory (sys.path)
377 :type tooldir: list of string
378 :param funs: unused variable
380 if isinstance(tool, list):
382 self.setup(i, tooldir)
385 module = Context.load_tool(tool, tooldir)
386 if hasattr(module, "setup"):
390 """Getter for the env property"""
392 return self.all_envs[self.variant]
394 return self.all_envs['']
395 def set_env(self, val):
396 """Setter for the env property"""
397 self.all_envs[self.variant] = val
399 env = property(get_env, set_env)
401 def add_manual_dependency(self, path, value):
403 Adds a dependency from a node object to a value::
406 bld.add_manual_dependency(
407 bld.path.find_resource('wscript'),
408 bld.root.find_resource('/etc/fstab'))
410 :param path: file path
411 :type path: string or :py:class:`waflib.Node.Node`
412 :param value: value to depend
413 :type value: :py:class:`waflib.Node.Node`, byte object, or function returning a byte object
416 raise ValueError('Invalid input path %r' % path)
418 if isinstance(path, Node.Node):
420 elif os.path.isabs(path):
421 node = self.root.find_resource(path)
423 node = self.path.find_resource(path)
425 raise ValueError('Could not find the path %r' % path)
427 if isinstance(value, list):
428 self.deps_man[node].extend(value)
430 self.deps_man[node].append(value)
432 def launch_node(self):
433 """Returns the launch directory as a :py:class:`waflib.Node.Node` object (cached)"""
437 except AttributeError:
438 self.p_ln = self.root.find_dir(self.launch_dir)
441 def hash_env_vars(self, env, vars_lst):
443 Hashes configuration set variables::
446 bld.hash_env_vars(bld.env, ['CXX', 'CC'])
448 This method uses an internal cache.
450 :param env: Configuration Set
451 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
452 :param vars_lst: list of variables
453 :type vars_list: list of string
461 idx = str(id(env)) + str(vars_lst)
463 cache = self.cache_env
464 except AttributeError:
465 cache = self.cache_env = {}
468 return self.cache_env[idx]
472 lst = [env[a] for a in vars_lst]
473 cache[idx] = ret = Utils.h_list(lst)
474 Logs.debug('envhash: %s %r', Utils.to_hex(ret), lst)
477 def get_tgen_by_name(self, name):
479 Fetches a task generator by its name or its target attribute;
480 the name must be unique in a build::
484 tg == bld.get_tgen_by_name('foo')
486 This method use a private internal cache.
488 :param name: Task generator name
489 :raises: :py:class:`waflib.Errors.WafError` in case there is no task genenerator by that name
491 cache = self.task_gen_cache_names
493 # create the index lazily
494 for g in self.groups:
498 except AttributeError:
499 # raised if not a task generator, which should be uncommon
504 raise Errors.WafError('Could not find a task generator for the name %r' % name)
506 def progress_line(self, idx, total, col1, col2):
508 Computes a progress bar line displayed when running ``waf -p``
510 :returns: progress bar line
513 if not sys.stderr.isatty():
519 ind = Utils.rot_chr[Utils.rot_idx % 4]
521 pc = (100. * idx)/total
522 fs = "[%%%dd/%%d][%%s%%2d%%%%%%s][%s][" % (n, ind)
523 left = fs % (idx, total, col1, pc, col2)
524 right = '][%s%s%s]' % (col1, self.timer, col2)
526 cols = Logs.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2)
530 ratio = ((cols * idx)//total) - 1
532 bar = ('='*ratio+'>').ljust(cols)
533 msg = Logs.indicator % (left, bar, right)
537 def declare_chain(self, *k, **kw):
539 Wraps :py:func:`waflib.TaskGen.declare_chain` for convenience
541 return TaskGen.declare_chain(*k, **kw)
544 """Executes user-defined methods before the build starts, see :py:meth:`waflib.Build.BuildContext.add_pre_fun`"""
545 for m in getattr(self, 'pre_funs', []):
548 def post_build(self):
549 """Executes user-defined methods after the build is successful, see :py:meth:`waflib.Build.BuildContext.add_post_fun`"""
550 for m in getattr(self, 'post_funs', []):
553 def add_pre_fun(self, meth):
555 Binds a callback method to execute after the scripts are read and before the build starts::
558 print("Hello, world!")
561 bld.add_pre_fun(mycallback)
564 self.pre_funs.append(meth)
565 except AttributeError:
566 self.pre_funs = [meth]
568 def add_post_fun(self, meth):
570 Binds a callback method to execute immediately after the build is successful::
572 def call_ldconfig(bld):
573 bld.exec_command('/sbin/ldconfig')
576 if bld.cmd == 'install':
577 bld.add_pre_fun(call_ldconfig)
580 self.post_funs.append(meth)
581 except AttributeError:
582 self.post_funs = [meth]
584 def get_group(self, x):
586 Returns the build group named `x`, or the current group if `x` is None
588 :param x: name or number or None
589 :type x: string, int or None
594 return self.groups[self.current_group]
595 if x in self.group_names:
596 return self.group_names[x]
597 return self.groups[x]
599 def add_to_group(self, tgen, group=None):
600 """Adds a task or a task generator to the build; there is no attempt to remove it if it was already added."""
601 assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.Task))
603 self.get_group(group).append(tgen)
605 def get_group_name(self, g):
607 Returns the name of the input build group
609 :param g: build group object or build group index
610 :type g: integer or list
614 if not isinstance(g, list):
616 for x in self.group_names:
617 if id(self.group_names[x]) == id(g):
621 def get_group_idx(self, tg):
623 Returns the index of the group containing the task generator given as argument::
626 tg = bld(name='nada')
627 0 == bld.get_group_idx(tg)
629 :param tg: Task generator object
630 :type tg: :py:class:`waflib.TaskGen.task_gen`
634 for i, tmp in enumerate(self.groups):
640 def add_group(self, name=None, move=True):
642 Adds a new group of tasks/task generators. By default the new group becomes
643 the default group for new task generators (make sure to create build groups in order).
645 :param name: name for this group
647 :param move: set this new group as default group (True by default)
649 :raises: :py:class:`waflib.Errors.WafError` if a group by the name given already exists
651 if name and name in self.group_names:
652 raise Errors.WafError('add_group: name %s already present', name)
654 self.group_names[name] = g
655 self.groups.append(g)
657 self.current_group = len(self.groups) - 1
659 def set_group(self, idx):
661 Sets the build group at position idx as current so that newly added
662 task generators are added to this one by default::
665 bld(rule='touch ${TGT}', target='foo.txt')
666 bld.add_group() # now the current group is 1
667 bld(rule='touch ${TGT}', target='bar.txt')
668 bld.set_group(0) # now the current group is 0
669 bld(rule='touch ${TGT}', target='truc.txt') # build truc.txt before bar.txt
671 :param idx: group name or group index
672 :type idx: string or int
674 if isinstance(idx, str):
675 g = self.group_names[idx]
676 for i, tmp in enumerate(self.groups):
678 self.current_group = i
681 self.current_group = idx
685 Approximate task count: this value may be inaccurate if task generators
686 are posted lazily (see :py:attr:`waflib.Build.BuildContext.post_mode`).
687 The value :py:attr:`waflib.Runner.Parallel.total` is updated during the task execution.
692 for group in self.groups:
695 total += len(tg.tasks)
696 except AttributeError:
700 def get_targets(self):
702 This method returns a pair containing the index of the last build group to post,
703 and the list of task generator objects corresponding to the target names.
705 This is used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
706 to perform partial builds::
708 $ waf --targets=myprogram,myshlib
710 :return: the minimum build group index, and list of task generators
715 for name in self.targets.split(','):
716 tg = self.get_tgen_by_name(name)
717 m = self.get_group_idx(tg)
723 return (min_grp, to_post)
725 def get_all_task_gen(self):
727 Returns a list of all task generators for troubleshooting purposes.
730 for g in self.groups:
734 def post_group(self):
736 Post task generators from the group indexed by self.current_group; used internally
737 by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
742 except AttributeError:
747 if self.targets == '*':
748 for tg in self.groups[self.current_group]:
751 if self.current_group < self._min_grp:
752 for tg in self.groups[self.current_group]:
755 for tg in self._exact_tg:
758 ln = self.launch_node()
759 if ln.is_child_of(self.bldnode):
760 Logs.warn('Building from the build directory, forcing --targets=*')
762 elif not ln.is_child_of(self.srcnode):
763 Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)', ln.abspath(), self.srcnode.abspath())
765 for tg in self.groups[self.current_group]:
768 except AttributeError:
771 if p.is_child_of(ln):
774 def get_tasks_group(self, idx):
776 Returns all task instances for the build group at position idx,
777 used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
779 :rtype: list of :py:class:`waflib.Task.Task`
782 for tg in self.groups[idx]:
784 tasks.extend(tg.tasks)
785 except AttributeError: # not a task generator
789 def get_build_iterator(self):
791 Creates a Python generator object that returns lists of tasks that may be processed in parallel.
793 :return: tasks which can be executed immediately
794 :rtype: generator returning lists of :py:class:`waflib.Task.Task`
796 if self.targets and self.targets != '*':
797 (self._min_grp, self._exact_tg) = self.get_targets()
799 if self.post_mode != POST_LAZY:
800 for self.current_group, _ in enumerate(self.groups):
803 for self.current_group, _ in enumerate(self.groups):
804 # first post the task generators for the group
805 if self.post_mode != POST_AT_ONCE:
808 # then extract the tasks
809 tasks = self.get_tasks_group(self.current_group)
811 # if the constraints are set properly (ext_in/ext_out, before/after)
812 # the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
813 # (but leave set_file_constraints for the installation step)
815 # if the tasks have only files, set_file_constraints is required but set_precedence_constraints is not necessary
817 Task.set_file_constraints(tasks)
818 Task.set_precedence_constraints(tasks)
820 self.cur_tasks = tasks
825 # the build stops once there are no tasks to process
828 def install_files(self, dest, files, **kw):
830 Creates a task generator to install files on the system::
833 bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
835 :param dest: path representing the destination directory
836 :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
837 :param files: input files
838 :type files: list of strings or list of :py:class:`waflib.Node.Node`
839 :param env: configuration set to expand *dest*
840 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
841 :param relative_trick: preserve the folder hierarchy when installing whole folders
842 :type relative_trick: bool
843 :param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
844 :type cwd: :py:class:`waflib.Node.Node`
845 :param postpone: execute the task immediately to perform the installation (False by default)
849 tg = self(features='install_task', install_to=dest, install_from=files, **kw)
850 tg.dest = tg.install_to
851 tg.type = 'install_files'
852 if not kw.get('postpone', True):
856 def install_as(self, dest, srcfile, **kw):
858 Creates a task generator to install a file on the system with a different name::
861 bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
863 :param dest: destination file
864 :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
865 :param srcfile: input file
866 :type srcfile: string or :py:class:`waflib.Node.Node`
867 :param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
868 :type cwd: :py:class:`waflib.Node.Node`
869 :param env: configuration set for performing substitutions in dest
870 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
871 :param postpone: execute the task immediately to perform the installation (False by default)
875 tg = self(features='install_task', install_to=dest, install_from=srcfile, **kw)
876 tg.dest = tg.install_to
877 tg.type = 'install_as'
878 if not kw.get('postpone', True):
882 def symlink_as(self, dest, src, **kw):
884 Creates a task generator to install a symlink::
887 bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
889 :param dest: absolute path of the symlink
890 :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
891 :param src: link contents, which is a relative or abolute path which may exist or not
893 :param env: configuration set for performing substitutions in dest
894 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
895 :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
897 :param postpone: execute the task immediately to perform the installation
899 :param relative_trick: make the symlink relative (default: ``False``)
900 :type relative_trick: bool
903 tg = self(features='install_task', install_to=dest, install_from=src, **kw)
904 tg.dest = tg.install_to
905 tg.type = 'symlink_as'
907 # TODO if add: self.add_to_group(tsk)
908 if not kw.get('postpone', True):
912 @TaskGen.feature('install_task')
913 @TaskGen.before_method('process_rule', 'process_source')
914 def process_install_task(self):
915 """Creates the installation task for the current task generator; uses :py:func:`waflib.Build.add_install_task` internally."""
916 self.add_install_task(**self.__dict__)
918 @TaskGen.taskgen_method
919 def add_install_task(self, **kw):
921 Creates the installation task for the current task generator, and executes it immediately if necessary
923 :returns: An installation task
924 :rtype: :py:class:`waflib.Build.inst`
926 if not self.bld.is_install:
928 if not kw['install_to']:
931 if kw['type'] == 'symlink_as' and Utils.is_win32:
932 if kw.get('win32_install'):
933 kw['type'] = 'install_as'
938 tsk = self.install_task = self.create_task('inst')
939 tsk.chmod = kw.get('chmod', Utils.O644)
940 tsk.link = kw.get('link', '') or kw.get('install_from', '')
941 tsk.relative_trick = kw.get('relative_trick', False)
942 tsk.type = kw['type']
943 tsk.install_to = tsk.dest = kw['install_to']
944 tsk.install_from = kw['install_from']
945 tsk.relative_base = kw.get('cwd') or kw.get('relative_base', self.path)
946 tsk.install_user = kw.get('install_user')
947 tsk.install_group = kw.get('install_group')
949 if not kw.get('postpone', True):
953 @TaskGen.taskgen_method
954 def add_install_files(self, **kw):
956 Creates an installation task for files
958 :returns: An installation task
959 :rtype: :py:class:`waflib.Build.inst`
961 kw['type'] = 'install_files'
962 return self.add_install_task(**kw)
964 @TaskGen.taskgen_method
965 def add_install_as(self, **kw):
967 Creates an installation task for a single file
969 :returns: An installation task
970 :rtype: :py:class:`waflib.Build.inst`
972 kw['type'] = 'install_as'
973 return self.add_install_task(**kw)
975 @TaskGen.taskgen_method
976 def add_symlink_as(self, **kw):
978 Creates an installation task for a symbolic link
980 :returns: An installation task
981 :rtype: :py:class:`waflib.Build.inst`
983 kw['type'] = 'symlink_as'
984 return self.add_install_task(**kw)
986 class inst(Task.Task):
987 """Task that installs files or symlinks; it is typically executed by :py:class:`waflib.Build.InstallContext` and :py:class:`waflib.Build.UnInstallContext`"""
989 """Returns an empty string to disable the standard task display"""
993 """Returns a unique identifier for the task"""
994 lst = self.inputs + self.outputs + [self.link, self.generator.path.abspath()]
995 return Utils.h_list(lst)
997 def init_files(self):
999 Initializes the task input and output nodes
1001 if self.type == 'symlink_as':
1004 inputs = self.generator.to_nodes(self.install_from)
1005 if self.type == 'install_as':
1006 assert len(inputs) == 1
1007 self.set_inputs(inputs)
1009 dest = self.get_install_path()
1011 if self.type == 'symlink_as':
1012 if self.relative_trick:
1013 self.link = os.path.relpath(self.link, os.path.dirname(dest))
1014 outputs.append(self.generator.bld.root.make_node(dest))
1015 elif self.type == 'install_as':
1016 outputs.append(self.generator.bld.root.make_node(dest))
1019 if self.relative_trick:
1020 destfile = os.path.join(dest, y.path_from(self.relative_base))
1022 destfile = os.path.join(dest, y.name)
1023 outputs.append(self.generator.bld.root.make_node(destfile))
1024 self.set_outputs(outputs)
1026 def runnable_status(self):
1028 Installation tasks are always executed, so this method returns either :py:const:`waflib.Task.ASK_LATER` or :py:const:`waflib.Task.RUN_ME`.
1030 ret = super(inst, self).runnable_status()
1031 if ret == Task.SKIP_ME and self.generator.bld.is_install:
1037 Disables any post-run operations
1041 def get_install_path(self, destdir=True):
1043 Returns the destination path where files will be installed, pre-pending `destdir`.
1047 if isinstance(self.install_to, Node.Node):
1048 dest = self.install_to.abspath()
1050 dest = Utils.subst_vars(self.install_to, self.env)
1051 if destdir and Options.options.destdir:
1052 dest = os.path.join(Options.options.destdir, os.path.splitdrive(dest)[1].lstrip(os.sep))
1055 def copy_fun(self, src, tgt):
1057 Copies a file from src to tgt, preserving permissions and trying to work
1058 around path limitations on Windows platforms. On Unix-like platforms,
1059 the owner/group of the target file may be set through install_user/install_group
1061 :param src: absolute path
1063 :param tgt: absolute path
1066 # override this if you want to strip executables
1067 # kw['tsk'].source is the task that created the files in the build
1068 if Utils.is_win32 and len(tgt) > 259 and not tgt.startswith('\\\\?\\'):
1069 tgt = '\\\\?\\' + tgt
1070 shutil.copy2(src, tgt)
1073 def rm_empty_dirs(self, tgt):
1075 Removes empty folders recursively when uninstalling.
1077 :param tgt: absolute path
1081 tgt = os.path.dirname(tgt)
1089 Performs file or symlink installation
1091 is_install = self.generator.bld.is_install
1092 if not is_install: # unnecessary?
1095 for x in self.outputs:
1096 if is_install == INSTALL:
1098 if self.type == 'symlink_as':
1099 fun = is_install == INSTALL and self.do_link or self.do_unlink
1100 fun(self.link, self.outputs[0].abspath())
1102 fun = is_install == INSTALL and self.do_install or self.do_uninstall
1103 launch_node = self.generator.bld.launch_node()
1104 for x, y in zip(self.inputs, self.outputs):
1105 fun(x.abspath(), y.abspath(), x.path_from(launch_node))
1109 Try executing the installation task right now
1111 :raises: :py:class:`waflib.Errors.TaskNotReady`
1113 status = self.runnable_status()
1114 if status not in (Task.RUN_ME, Task.SKIP_ME):
1115 raise Errors.TaskNotReady('Could not process %r: status %r' % (self, status))
1117 self.hasrun = Task.SUCCESS
1119 def do_install(self, src, tgt, lbl, **kw):
1121 Copies a file from src to tgt with given file permissions. The actual copy is only performed
1122 if the source and target file sizes or timestamps differ. When the copy occurs,
1123 the file is always first removed and then copied so as to prevent stale inodes.
1125 :param src: file name as absolute path
1127 :param tgt: file destination, as absolute path
1129 :param lbl: file source description
1131 :param chmod: installation mode
1133 :raises: :py:class:`waflib.Errors.WafError` if the file cannot be written
1135 if not Options.options.force:
1136 # check if the file is already there to avoid a copy
1143 # same size and identical timestamps -> make no copy
1144 if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
1145 if not self.generator.bld.progress_bar:
1146 Logs.info('- install %s (from %s)', tgt, lbl)
1149 if not self.generator.bld.progress_bar:
1150 Logs.info('+ install %s (from %s)', tgt, lbl)
1152 # Give best attempt at making destination overwritable,
1153 # like the 'install' utility used by 'make install' does.
1155 os.chmod(tgt, Utils.O644 | stat.S_IMODE(os.stat(tgt).st_mode))
1156 except EnvironmentError:
1159 # following is for shared libs and stale inodes (-_-)
1166 self.copy_fun(src, tgt)
1167 except EnvironmentError as e:
1168 if not os.path.exists(src):
1169 Logs.error('File %r does not exist', src)
1170 elif not os.path.isfile(src):
1171 Logs.error('Input %r is not a file', src)
1172 raise Errors.WafError('Could not install the file %r' % tgt, e)
1174 def fix_perms(self, tgt):
1176 Change the ownership of the file/folder/link pointed by the given path
1177 This looks up for `install_user` or `install_group` attributes
1178 on the task or on the task generator::
1181 bld.install_as('${PREFIX}/wscript',
1183 install_user='nobody', install_group='nogroup')
1184 bld.symlink_as('${PREFIX}/wscript_link',
1185 Utils.subst_vars('${PREFIX}/wscript', bld.env),
1186 install_user='nobody', install_group='nogroup')
1188 if not Utils.is_win32:
1189 user = getattr(self, 'install_user', None) or getattr(self.generator, 'install_user', None)
1190 group = getattr(self, 'install_group', None) or getattr(self.generator, 'install_group', None)
1192 Utils.lchown(tgt, user or -1, group or -1)
1193 if not os.path.islink(tgt):
1194 os.chmod(tgt, self.chmod)
1196 def do_link(self, src, tgt, **kw):
1198 Creates a symlink from tgt to src.
1200 :param src: file name as absolute path
1202 :param tgt: file destination, as absolute path
1205 if os.path.islink(tgt) and os.readlink(tgt) == src:
1206 if not self.generator.bld.progress_bar:
1207 Logs.info('- symlink %s (to %s)', tgt, src)
1213 if not self.generator.bld.progress_bar:
1214 Logs.info('+ symlink %s (to %s)', tgt, src)
1215 os.symlink(src, tgt)
1218 def do_uninstall(self, src, tgt, lbl, **kw):
1220 See :py:meth:`waflib.Build.inst.do_install`
1222 if not self.generator.bld.progress_bar:
1223 Logs.info('- remove %s', tgt)
1225 #self.uninstall.append(tgt)
1228 except OSError as e:
1229 if e.errno != errno.ENOENT:
1230 if not getattr(self, 'uninstall_error', None):
1231 self.uninstall_error = True
1232 Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)')
1233 if Logs.verbose > 1:
1234 Logs.warn('Could not remove %s (error code %r)', e.filename, e.errno)
1235 self.rm_empty_dirs(tgt)
1237 def do_unlink(self, src, tgt, **kw):
1239 See :py:meth:`waflib.Build.inst.do_link`
1242 if not self.generator.bld.progress_bar:
1243 Logs.info('- remove %s', tgt)
1247 self.rm_empty_dirs(tgt)
1249 class InstallContext(BuildContext):
1250 '''installs the targets on the system'''
1253 def __init__(self, **kw):
1254 super(InstallContext, self).__init__(**kw)
1255 self.is_install = INSTALL
1257 class UninstallContext(InstallContext):
1258 '''removes the targets installed'''
1261 def __init__(self, **kw):
1262 super(UninstallContext, self).__init__(**kw)
1263 self.is_install = UNINSTALL
1265 class CleanContext(BuildContext):
1266 '''cleans the project'''
1270 See :py:func:`waflib.Build.BuildContext.execute`.
1273 if not self.all_envs:
1276 self.recurse([self.run_dir])
1284 Remove most files from the build directory, and reset all caches.
1286 Custom lists of files to clean can be declared as `bld.clean_files`.
1287 For example, exclude `build/program/myprogram` from getting removed::
1290 bld.clean_files = bld.bldnode.ant_glob('**',
1291 excl='.lock* config.log c4che/* config.h program/myprogram',
1292 quiet=True, generator=True)
1294 Logs.debug('build: clean called')
1296 if hasattr(self, 'clean_files'):
1297 for n in self.clean_files:
1299 elif self.bldnode != self.srcnode:
1300 # would lead to a disaster if top == out
1302 for env in self.all_envs.values():
1303 lst.extend(self.root.find_or_declare(f) for f in env[CFG_FILES])
1304 for n in self.bldnode.ant_glob('**/*', excl='.lock* *conf_check_*/** config.log c4che/*', quiet=True):
1308 self.root.children = {}
1310 for v in SAVED_ATTRS:
1313 setattr(self, v, {})
1315 class ListContext(BuildContext):
1316 '''lists the targets to execute'''
1321 In addition to printing the name of each build target,
1322 a description column will include text for each task
1323 generator which has a "description" field set.
1325 See :py:func:`waflib.Build.BuildContext.execute`.
1328 if not self.all_envs:
1331 self.recurse([self.run_dir])
1334 # display the time elapsed in the progress bar
1335 self.timer = Utils.Timer()
1337 for g in self.groups:
1341 except AttributeError:
1347 # force the cache initialization
1348 self.get_tgen_by_name('')
1349 except Errors.WafError:
1352 targets = sorted(self.task_gen_cache_names)
1354 # figure out how much to left-justify, for largest target name
1355 line_just = max(len(t) for t in targets) if targets else 0
1357 for target in targets:
1358 tgen = self.task_gen_cache_names[target]
1360 # Support displaying the description for the target
1361 # if it was set on the tgen
1362 descript = getattr(tgen, 'description', '')
1364 target = target.ljust(line_just)
1365 descript = ': %s' % descript
1367 Logs.pprint('GREEN', target, label=descript)
1369 class StepContext(BuildContext):
1370 '''executes tasks in a step-by-step fashion, for debugging'''
1373 def __init__(self, **kw):
1374 super(StepContext, self).__init__(**kw)
1375 self.files = Options.options.files
1379 Overrides :py:meth:`waflib.Build.BuildContext.compile` to perform a partial build
1380 on tasks matching the input/output pattern given (regular expression matching)::
1382 $ waf step --files=foo.c,bar.c,in:truc.c,out:bar.o
1383 $ waf step --files=in:foo.cpp.1.o # link task only
1387 Logs.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
1388 BuildContext.compile(self)
1392 if self.targets and self.targets != '*':
1393 targets = self.targets.split(',')
1395 for g in self.groups:
1397 if targets and tg.name not in targets:
1402 except AttributeError:
1407 for pat in self.files.split(','):
1408 matcher = self.get_matcher(pat)
1410 if isinstance(tg, Task.Task):
1416 for node in tsk.inputs:
1417 if matcher(node, output=False):
1420 for node in tsk.outputs:
1421 if matcher(node, output=True):
1426 Logs.info('%s -> exit %r', tsk, ret)
1428 def get_matcher(self, pat):
1430 Converts a step pattern into a function
1432 :param: pat: pattern of the form in:truc.c,out:bar.o
1433 :returns: Python function that uses Node objects as inputs and returns matches
1436 # this returns a function
1439 if pat.startswith('in:'):
1441 pat = pat.replace('in:', '')
1442 elif pat.startswith('out:'):
1444 pat = pat.replace('out:', '')
1446 anode = self.root.find_node(pat)
1449 if not pat.startswith('^'):
1450 pat = '^.+?%s' % pat
1451 if not pat.endswith('$'):
1453 pattern = re.compile(pat)
1455 def match(node, output):
1456 if output and not out:
1458 if not output and not inn:
1462 return anode == node
1464 return pattern.match(node.abspath())
1467 class EnvContext(BuildContext):
1468 """Subclass EnvContext to create commands that require configuration data in 'env'"""
1472 See :py:func:`waflib.Build.BuildContext.execute`.
1475 if not self.all_envs:
1477 self.recurse([self.run_dir])