3 # Thomas Nagy, 2005-2010 (ita)
6 Classes related to the build phase (build, clean, install, step, etc)
8 The inheritance tree is the following:
12 import os
, sys
, errno
, re
, shutil
, stat
16 import pickle
as cPickle
17 from waflib
import Runner
, TaskGen
, Utils
, ConfigSet
, Task
, Logs
, Options
, Context
, Errors
21 """Location of the cache files"""
23 CACHE_SUFFIX
= '_cache.py'
24 """Suffix for the cache files"""
27 """Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
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)"""
39 """Post mode: all task generators are posted before the build really starts"""
42 """Post mode: post the task generators group after group"""
45 """Post mode: post the task generators at once, then re-check them for each group"""
48 if sys
.platform
== 'cli':
51 class BuildContext(Context
.Context
):
52 '''executes the build'''
57 def __init__(self
, **kw
):
58 super(BuildContext
, self
).__init
__(**kw
)
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
80 # ======================================= #
84 """Signatures of the tasks (persists between build executions)"""
87 """Dict of node dependencies found by :py:meth:`waflib.Task.Task.scan` (persists between build executions)"""
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
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"""
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::
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
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))
158 def rule(self
, *k
, **kw
):
160 Wrapper for creating a task generator using the decorator notation. The following code::
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`"""
192 def install_as(self
, *k
, **kw
):
193 """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.install_as`"""
196 def symlink_as(self
, *k
, **kw
):
197 """Actual implementation provided by :py:meth:`waflib.Build.InstallContext.symlink_as`"""
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
)
208 raise Errors
.WafError('The project was not configured: run "waf configure" first!')
209 lst
= node
.ant_glob('**/*%s' % CACHE_SUFFIX
, quiet
=True)
212 raise Errors
.WafError('The cache directory is empty: reconfigure the project')
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
)
221 h
= Utils
.h_file(newnode
.abspath())
222 except (IOError, AttributeError):
223 Logs
.error('cannot find %r' % f
)
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
)
244 Restore the data from previous builds and call :py:meth:`waflib.Build.BuildContext.execute_build`. Overrides from :py:func:`waflib.Context.Context.execute`
247 if not self
.all_envs
:
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
])
266 # display the time elapsed in the progress bar
267 self
.timer
= Utils
.Timer()
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
)
281 Load the data from a previous run, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`
284 env
= ConfigSet
.ConfigSet(os
.path
.join(self
.cache_dir
, 'build.config.py'))
285 except EnvironmentError:
288 if env
['version'] < Context
.HEXVERSION
:
289 raise Errors
.WafError('Version mismatch! reconfigure the project')
290 for t
in env
['tools']:
293 dbfn
= os
.path
.join(self
.variant_dir
, Context
.DBFILE
)
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
)
301 waflib
.Node
.pickle_lock
.acquire()
302 waflib
.Node
.Nod3
= self
.node_class
304 data
= cPickle
.loads(data
)
305 except Exception as e
:
306 Logs
.debug('build: Could not pickle the build cache %s: %r' % (dbfn
, e
))
308 for x
in SAVED_ATTRS
:
309 setattr(self
, x
, data
[x
])
311 waflib
.Node
.pickle_lock
.release()
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.
322 for x
in SAVED_ATTRS
:
323 data
[x
] = getattr(self
, x
)
324 db
= os
.path
.join(self
.variant_dir
, Context
.DBFILE
)
327 waflib
.Node
.pickle_lock
.acquire()
328 waflib
.Node
.Nod3
= self
.node_class
329 x
= cPickle
.dumps(data
, PROTOCOL
)
331 waflib
.Node
.pickle_lock
.release()
333 Utils
.writef(db
+ '.tmp', x
, m
='wb')
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):
343 # do not use shutil.move (copy is not thread-safe)
344 os
.rename(db
+ '.tmp', db
)
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
358 self
.producer
.start()
359 except KeyboardInterrupt:
363 if self
.producer
.dirty
:
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::
377 pass # glib2 is imported implicitly
379 :param tool: 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
)
389 module
= Context
.load_tool(tool
, tooldir
)
390 if hasattr(module
, "setup"): module
.setup(self
)
393 """Getter for the env property"""
395 return self
.all_envs
[self
.variant
]
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::
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
419 raise ValueError('Invalid input')
421 if isinstance(path
, waflib
.Node
.Node
):
423 elif os
.path
.isabs(path
):
424 node
= self
.root
.find_resource(path
)
426 node
= self
.path
.find_resource(path
)
428 if isinstance(value
, list):
429 self
.deps_man
[id(node
)].extend(value
)
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"""
438 except AttributeError:
439 self
.p_ln
= self
.root
.find_dir(self
.launch_dir
)
442 def hash_env_vars(self
, env
, vars_lst
):
444 Hash configuration set variables::
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
460 idx
= str(id(env
)) + str(vars_lst
)
462 cache
= self
.cache_env
463 except AttributeError:
464 cache
= self
.cache_env
= {}
467 return self
.cache_env
[idx
]
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
)
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::
486 tg == bld.get_tgen_by_name('foo')
488 cache
= self
.task_gen_cache_names
490 # create the index lazily
491 for g
in self
.groups
:
495 except AttributeError:
496 # raised if not a task generator, which should be uncommon
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():
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
)
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
)
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', []):
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', []):
547 def add_pre_fun(self
, meth
):
549 Bind a method to execute after the scripts are read and before the build starts::
552 print("Hello, world!")
555 bld.add_pre_fun(mycallback)
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')
570 if bld.cmd == 'install':
571 bld.add_pre_fun(call_ldconfig)
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
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"""
596 assert(isinstance(tgen
, TaskGen
.task_gen
) or isinstance(tgen
, Task
.TaskBase
))
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):
604 for x
in self
.group_names
:
605 if id(self
.group_names
[x
]) == id(g
):
609 def get_group_idx(self
, tg
):
611 Index of the group containing the task generator given as argument::
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`
621 for i
in range(len(self
.groups
)):
622 for t
in self
.groups
[i
]:
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
633 :param move: set the group created as default group (True by default)
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
)
641 self
.group_names
[name
] = g
642 self
.groups
.append(g
)
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::
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
667 self
.current_group
= idx
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.
675 for group
in self
.groups
:
678 total
+= len(tg
.tasks
)
679 except AttributeError:
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
691 for name
in self
.targets
.split(','):
692 tg
= self
.get_tgen_by_name(name
)
693 m
= self
.get_group_idx(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
706 for g
in self
.groups
:
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
]:
718 except AttributeError:
723 if self
.cur
< self
._min
_grp
:
724 for tg
in self
.groups
[self
.cur
]:
727 except AttributeError:
732 for tg
in self
._exact
_tg
:
735 ln
= self
.launch_node()
736 if ln
.is_child_of(self
.bldnode
):
737 Logs
.warn('Building from the build directory, forcing --targets=*')
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()))
742 for tg
in self
.groups
[self
.cur
]:
745 except AttributeError:
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`
756 for tg
in self
.groups
[idx
]:
758 tasks
.extend(tg
.tasks
)
759 except AttributeError: # not a task generator, can be the case for installation 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`
772 if self
.targets
and self
.targets
!= '*':
773 (self
._min
_grp
, self
._exact
_tg
) = self
.get_targets()
776 if self
.post_mode
!= POST_LAZY
:
777 while self
.cur
< len(self
.groups
):
782 while self
.cur
< len(self
.groups
):
783 # first post the task generators for the group
784 if self
.post_mode
!= POST_AT_ONCE
:
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
800 if not tasks
: # return something else the build will stop
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
814 lst
= [self
.dest
, self
.path
] + self
.source
815 return Utils
.h_list(repr(lst
))
819 Same interface as in :py:meth:`waflib.TaskGen.task_gen.post`
822 for x
in self
.source
:
823 if isinstance(x
, waflib
.Node
.Node
):
826 y
= self
.path
.find_resource(x
)
829 y
= self
.bld
.root
.make_node(x
)
831 y
= self
.path
.make_node(x
)
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
:
845 """Return an empty string to disable the display"""
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
))
863 def exec_install_files(self
):
865 Predefined method for installing files
867 destpath
= self
.get_install_path()
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
))
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()
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'''
898 def __init__(self
, **kw
):
899 super(InstallContext
, self
).__init
__(**kw
)
901 # list of targets to uninstall for removing the empty folders after uninstalling
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
923 :param tgt: file destination, as absolute path
925 :param chmod: installation mode
928 d
, _
= os
.path
.split(tgt
)
930 raise Errors
.WafError('Invalid installation given %r->%r' % (src
, tgt
))
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
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
))
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.
954 os
.chmod(tgt
, Utils
.O644 | stat
.S_IMODE(os
.stat(tgt
).st_mode
))
955 except EnvironmentError:
958 # following is for shared libs and stale inodes (-_-)
965 self
.copy_fun(src
, tgt
, **kw
)
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
981 :param tgt: file destination, as absolute path
984 d
, _
= os
.path
.split(tgt
)
988 if not os
.path
.islink(tgt
):
990 elif os
.readlink(tgt
) != src
:
996 if not self
.progress_bar
:
997 Logs
.info('+ symlink %s (to %s)' % (tgt
, src
))
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.
1012 if tsk
.runnable_status() == Task
.ASK_LATER
:
1013 raise self
.WafError('cannot post the task %r' % tsk
)
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::
1022 bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
1024 :param dest: absolute path of the destination directory
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
1036 :param postpone: execute the task immediately to perform the installation
1037 :type postpone: bool
1040 tsk
= inst(env
=env
or self
.env
)
1042 tsk
.path
= cwd
or self
.path
1045 if isinstance(files
, waflib
.Node
.Node
):
1046 tsk
.source
= [files
]
1048 tsk
.source
= Utils
.to_list(files
)
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
)
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::
1061 bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
1063 :param dest: absolute path of the destination file
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
1073 :param postpone: execute the task immediately to perform the installation
1074 :type postpone: bool
1077 tsk
= inst(env
=env
or self
.env
)
1079 tsk
.path
= cwd
or self
.path
1081 tsk
.source
= [srcfile
]
1084 tsk
.exec_task
= tsk
.exec_install_as
1085 if add
: self
.add_to_group(tsk
)
1086 self
.run_task_now(tsk
, postpone
)
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::
1094 bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
1096 :param dest: absolute path of the symlink
1098 :param src: absolute or relative path of the link
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
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
1110 # symlinks *cannot* work on that platform
1111 # TODO waf 1.9 - replace by install_as
1114 tsk
= inst(env
=env
or self
.env
)
1117 tsk
.path
= cwd
or self
.path
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
)
1127 class UninstallContext(InstallContext
):
1128 '''removes the targets installed'''
1131 def __init__(self
, **kw
):
1132 super(UninstallContext
, self
).__init
__(**kw
)
1133 self
.is_install
= UNINSTALL
1135 def rm_empty_dirs(self
, tgt
):
1137 tgt
= os
.path
.dirname(tgt
)
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
)
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`"""
1164 if not self
.progress_bar
:
1165 Logs
.info('- remove %s' % tgt
)
1170 self
.rm_empty_dirs(tgt
)
1174 See :py:func:`waflib.Context.Context.execute`
1177 # do not execute any tasks
1178 def runnable_status(self
):
1180 setattr(Task
.Task
, 'runnable_status_back', Task
.Task
.runnable_status
)
1181 setattr(Task
.Task
, 'runnable_status', runnable_status
)
1183 super(UninstallContext
, self
).execute()
1185 setattr(Task
.Task
, 'runnable_status', Task
.Task
.runnable_status_back
)
1187 class CleanContext(BuildContext
):
1188 '''cleans the project'''
1192 See :py:func:`waflib.Context.Context.execute`
1195 if not self
.all_envs
:
1198 self
.recurse([self
.run_dir
])
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
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):
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'''
1228 See :py:func:`waflib.Context.Context.execute`.
1231 if not self
.all_envs
:
1234 self
.recurse([self
.run_dir
])
1237 # display the time elapsed in the progress bar
1238 self
.timer
= Utils
.Timer()
1240 for g
in self
.groups
:
1244 except AttributeError:
1250 # force the cache initialization
1251 self
.get_tgen_by_name('')
1254 lst
= list(self
.task_gen_cache_names
.keys())
1257 Logs
.pprint('GREEN', k
)
1259 class StepContext(BuildContext
):
1260 '''executes tasks in a step-by-step fashion, for debugging'''
1263 def __init__(self
, **kw
):
1264 super(StepContext
, self
).__init
__(**kw
)
1265 self
.files
= Options
.options
.files
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
1276 Logs
.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
1277 BuildContext
.compile(self
)
1281 if self
.targets
and self
.targets
!= '*':
1282 targets
= self
.targets
.split(',')
1284 for g
in self
.groups
:
1286 if targets
and tg
.name
not in targets
:
1291 except AttributeError:
1296 for pat
in self
.files
.split(','):
1297 matcher
= self
.get_matcher(pat
)
1299 if isinstance(tg
, Task
.TaskBase
):
1305 for node
in getattr(tsk
, 'inputs', []):
1306 if matcher(node
, output
=False):
1309 for node
in getattr(tsk
, 'outputs', []):
1310 if matcher(node
, output
=True):
1315 Logs
.info('%s -> exit %r' % (str(tsk
), ret
))
1317 def get_matcher(self
, pat
):
1318 # this returns a function
1321 if pat
.startswith('in:'):
1323 pat
= pat
.replace('in:', '')
1324 elif pat
.startswith('out:'):
1326 pat
= pat
.replace('out:', '')
1328 anode
= self
.root
.find_node(pat
)
1331 if not pat
.startswith('^'):
1332 pat
= '^.+?%s' % pat
1333 if not pat
.endswith('$'):
1335 pattern
= re
.compile(pat
)
1337 def match(node
, output
):
1338 if output
== True and not out
:
1340 if output
== False and not inn
:
1344 return anode
== node
1346 return pattern
.match(node
.abspath())