3 # Thomas Nagy, 2005 (ita)
8 The class Build holds all the info related to a build:
9 * file system representation (tree of Node instances)
10 * various cached objects (task signatures, file scan results, ..)
12 There is only one Build object at a time (bld singleton)
15 import os
, sys
, errno
, re
, glob
, gc
, datetime
, shutil
17 except: import pickle
as cPickle
18 import Runner
, TaskGen
, Node
, Scripting
, Utils
, Environment
, Task
, Logs
, Options
19 from Logs
import debug
, error
, info
20 from Constants
import *
22 SAVED_ATTRS
= 'root srcnode bldnode node_sigs node_deps raw_deps task_sigs id_nodes'.split()
23 "Build class members to save"
26 "singleton - safe to use when Waf is not used as a library"
28 class BuildError(Utils
.WafError
):
29 def __init__(self
, b
=None, t
=[]):
33 Utils
.WafError
.__init
__(self
, self
.format_error())
35 def format_error(self
):
36 lst
= ['Build failed:']
37 for tsk
in self
.tasks
:
38 txt
= tsk
.format_error()
39 if txt
: lst
.append(txt
)
45 def group_method(fun
):
47 sets a build context method to execute after the current group has finished executing
48 this is useful for installing build files:
49 * calling install_files/install_as will fail if called too early
50 * people do not want to define install method in their task classes
55 if not k
[0].is_install
:
60 postpone
= kw
['postpone']
63 # TODO waf 1.6 in theory there should be no reference to the TaskManager internals here
66 if not m
.groups
: m
.add_group()
67 m
.groups
[m
.current_group
].post_funs
.append((fun
, k
, kw
))
74 class BuildContext(Utils
.Context
):
75 "holds the dependency tree"
78 # not a singleton, but provided for compatibility
82 self
.task_manager
= Task
.TaskManager()
84 # instead of hashing the nodes, we assign them a unique id when they are created
88 # map names to environments, the 'default' must be defined
91 # ======================================= #
92 # code for reading the scripts
94 # project build directory - do not reset() from load_dirs()
97 # the current directory from which the code is run
98 # the folder changes everytime a wscript is read
101 # Manual dependencies.
102 self
.deps_man
= Utils
.DefaultDict(list)
104 # ======================================= #
107 # local cache for absolute paths - cache_node_abspath[variant][node]
108 self
.cache_node_abspath
= {}
110 # list of folders that are already scanned
111 # so that we do not need to stat them one more time
112 self
.cache_scanned_folders
= {}
114 # list of targets to uninstall for removing the empty folders after uninstalling
117 # ======================================= #
120 # build dir variants (release, debug, ..)
121 for v
in 'cache_node_abspath task_sigs node_deps raw_deps node_sigs'.split():
123 setattr(self
, v
, var
)
125 self
.cache_dir_contents
= {}
127 self
.all_task_gen
= []
128 self
.task_gen_cache_names
= {}
129 self
.cache_sig_vars
= {}
136 # bind the build context to the nodes in use
137 # this means better encapsulation and no build context singleton
138 class node_class(Node
.Node
):
140 self
.node_class
= node_class
141 self
.node_class
.__module
__ = "Node"
142 self
.node_class
.__name
__ = "Nodu"
143 self
.node_class
.bld
= self
145 self
.is_install
= None
148 "nodes are not supposed to be copied"
149 raise Utils
.WafError('build contexts are not supposed to be cloned')
152 "load the cache from the disk"
154 env
= Environment
.Environment(os
.path
.join(self
.cachedir
, 'build.config.py'))
155 except (IOError, OSError):
158 if env
['version'] < HEXVERSION
:
159 raise Utils
.WafError('Version mismatch! reconfigure the project')
160 for t
in env
['tools']:
167 Node
.Nodu
= self
.node_class
170 f
= open(os
.path
.join(self
.bdir
, DBFILE
), 'rb')
171 except (IOError, EOFError):
172 # handle missing file/empty file
176 if f
: data
= cPickle
.load(f
)
177 except AttributeError:
178 # handle file of an old Waf version
179 # that has an attribute which no longer exist
180 # (e.g. AttributeError: 'module' object has no attribute 'BuildDTO')
181 if Logs
.verbose
> 1: raise
184 for x
in SAVED_ATTRS
: setattr(self
, x
, data
[x
])
186 debug('build: Build cache loading failed')
193 "store the cache on disk, see self.load"
195 self
.root
.__class
__.bld
= None
197 # some people are very nervous with ctrl+c so we have to make a temporary file
198 Node
.Nodu
= self
.node_class
199 db
= os
.path
.join(self
.bdir
, DBFILE
)
200 file = open(db
+ '.tmp', 'wb')
202 for x
in SAVED_ATTRS
: data
[x
] = getattr(self
, x
)
203 cPickle
.dump(data
, file, -1)
206 # do not use shutil.move
209 os
.rename(db
+ '.tmp', db
)
210 self
.root
.__class
__.bld
= self
213 # ======================================= #
216 debug('build: clean called')
218 # does not clean files created during the configuration
220 for env
in self
.all_envs
.values():
221 for x
in env
[CFG_FILES
]:
222 node
= self
.srcnode
.find_resource(x
)
224 precious
.add(node
.id)
227 for x
in list(node
.childs
.keys()):
233 elif tp
== Node
.BUILD
:
234 if nd
.id in precious
: continue
235 for env
in self
.all_envs
.values():
236 try: os
.remove(nd
.abspath(env
))
238 node
.childs
.__delitem
__(x
)
240 clean_rec(self
.srcnode
)
242 for v
in 'node_sigs node_deps task_sigs raw_deps cache_node_abspath'.split():
246 """The cache file is not written if nothing was build at all (build is up to date)"""
247 debug('build: compile called')
250 import cProfile, pstats
251 cProfile.run("import Build\nBuild.bld.flush()", 'profi.txt')
252 p = pstats.Stats('profi.txt')
253 p.sort_stats('cumulative').print_stats(80)
258 self
.generator
= Runner
.Parallel(self
, Options
.options
.jobs
)
261 if Options
.options
.progress_bar
:
262 if on
: sys
.stderr
.write(Logs
.colors
.cursor_on
)
263 else: sys
.stderr
.write(Logs
.colors
.cursor_off
)
265 debug('build: executor starting')
268 os
.chdir(self
.bldnode
.abspath())
273 self
.generator
.start()
274 except KeyboardInterrupt:
276 # if self.generator.processed != 1: TODO
281 # do not store anything, for something bad happened
285 #if self.generator.processed != 1: TODO
288 if self
.generator
.error
:
289 raise BuildError(self
, self
.task_manager
.tasks_done
)
295 "this function is called for both install and uninstall"
296 debug('build: install called')
300 # remove empty folders after uninstalling
301 if self
.is_install
< 0:
303 for x
in self
.uninstall
:
304 dir = os
.path
.dirname(x
)
305 if not dir in lst
: lst
.append(dir)
313 if not x
in nlst
: nlst
.append(x
)
314 x
= os
.path
.dirname(x
)
322 def new_task_gen(self
, *k
, **kw
):
323 if self
.task_gen_cache_names
:
324 self
.task_gen_cache_names
= {}
328 ret
= TaskGen
.task_gen(*k
, **kw
)
332 try: cls
= TaskGen
.task_gen
.classes
[cls_name
]
333 except KeyError: raise Utils
.WscriptError('%s is not a valid task generator -> %s' %
334 (cls_name
, [x
for x
in TaskGen
.task_gen
.classes
]))
338 def __call__(self
, *k
, **kw
):
339 if self
.task_gen_cache_names
:
340 self
.task_gen_cache_names
= {}
343 return TaskGen
.task_gen(*k
, **kw
)
347 lst
= Utils
.listdir(self
.cachedir
)
349 if e
.errno
== errno
.ENOENT
:
350 raise Utils
.WafError('The project was not configured: run "waf configure" first!')
355 raise Utils
.WafError('The cache directory is empty: reconfigure the project')
358 if file.endswith(CACHE_SUFFIX
):
359 env
= Environment
.Environment(os
.path
.join(self
.cachedir
, file))
360 name
= file[:-len(CACHE_SUFFIX
)]
362 self
.all_envs
[name
] = env
366 for env
in self
.all_envs
.values():
367 for f
in env
[CFG_FILES
]:
368 newnode
= self
.path
.find_or_declare(f
)
370 hash = Utils
.h_file(newnode
.abspath(env
))
371 except (IOError, AttributeError):
372 error("cannot find "+f
)
374 self
.node_sigs
[env
.variant()][newnode
.id] = hash
376 # TODO: hmmm, these nodes are removed from the tree when calling rescan()
377 self
.bldnode
= self
.root
.find_dir(self
.bldnode
.abspath())
378 self
.path
= self
.srcnode
= self
.root
.find_dir(self
.srcnode
.abspath())
379 self
.cwd
= self
.bldnode
.abspath()
381 def setup(self
, tool
, tooldir
=None, funs
=None):
382 "setup tools for build process"
383 if isinstance(tool
, list):
384 for i
in tool
: self
.setup(i
, tooldir
)
387 if not tooldir
: tooldir
= Options
.tooldir
389 module
= Utils
.load_tool(tool
, tooldir
)
390 if hasattr(module
, "setup"): module
.setup(self
)
392 def init_variants(self
):
393 debug('build: init variants')
396 for env
in self
.all_envs
.values():
397 if not env
.variant() in lstvariants
:
398 lstvariants
.append(env
.variant())
399 self
.lst_variants
= lstvariants
401 debug('build: list of variants is %r', lstvariants
)
403 for name
in lstvariants
+[0]:
404 for v
in 'node_sigs cache_node_abspath'.split():
405 var
= getattr(self
, v
)
409 # ======================================= #
410 # node and folder handling
412 # this should be the main entry point
413 def load_dirs(self
, srcdir
, blddir
, load_cache
=1):
414 "this functions should be the start of everything"
416 assert(os
.path
.isabs(srcdir
))
417 assert(os
.path
.isabs(blddir
))
419 self
.cachedir
= os
.path
.join(blddir
, CACHE_DIR
)
422 raise Utils
.WafError("build dir must be different from srcdir: %s <-> %s " % (srcdir
, blddir
))
426 # try to load the cache file, if it does not exist, nothing happens
430 Node
.Nodu
= self
.node_class
431 self
.root
= Node
.Nodu('', None, Node
.DIR
)
434 self
.srcnode
= self
.root
.ensure_dir_node_from_path(srcdir
)
435 debug('build: srcnode is %s and srcdir %s', self
.srcnode
.name
, srcdir
)
437 self
.path
= self
.srcnode
439 # create this build dir if necessary
440 try: os
.makedirs(blddir
)
444 self
.bldnode
= self
.root
.ensure_dir_node_from_path(blddir
)
448 def rescan(self
, src_dir_node
):
450 look the contents of a (folder)node and update its list of childs
452 The intent is to perform the following steps
453 * remove the nodes for the files that have disappeared
454 * remove the signatures for the build files that have disappeared
455 * cache the results of os.listdir
456 * create the build folder equivalent (mkdir) for each variant
457 src/bar -> build/default/src/bar, build/release/src/bar
459 when a folder in the source directory is removed, we do not check recursively
460 to remove the unused nodes. To do that, call 'waf clean' and build again.
463 # do not rescan over and over again
464 # TODO use a single variable in waf 1.6
465 if self
.cache_scanned_folders
.get(src_dir_node
.id, None): return
466 self
.cache_scanned_folders
[src_dir_node
.id] = True
468 # TODO remove in waf 1.6
469 if hasattr(self
, 'repository'): self
.repository(src_dir_node
)
471 if not src_dir_node
.name
and sys
.platform
== 'win32':
472 # the root has no name, contains drive letters, and cannot be listed
476 # first, take the case of the source directory
477 parent_path
= src_dir_node
.abspath()
479 lst
= set(Utils
.listdir(parent_path
))
483 # TODO move this at the bottom
484 self
.cache_dir_contents
[src_dir_node
.id] = lst
486 # hash the existing source files, remove the others
487 cache
= self
.node_sigs
[0]
488 for x
in src_dir_node
.childs
.values():
489 if x
.id & 3 != Node
.FILE
: continue
492 cache
[x
.id] = Utils
.h_file(x
.abspath())
494 raise Utils
.WafError('The file %s is not readable or has become a dir' % x
.abspath())
497 except KeyError: pass
499 del src_dir_node
.childs
[x
.name
]
502 # first obtain the differences between srcnode and src_dir_node
503 h1
= self
.srcnode
.height()
504 h2
= src_dir_node
.height()
509 lst
.append(child
.name
)
514 # list the files in the build dirs
516 for variant
in self
.lst_variants
:
517 sub_path
= os
.path
.join(self
.bldnode
.abspath(), variant
, *lst
)
518 self
.listdir_bld(src_dir_node
, sub_path
, variant
)
521 # listdir failed, remove the build node signatures for all variants
522 for node
in src_dir_node
.childs
.values():
523 if node
.id & 3 != Node
.BUILD
:
526 for dct
in self
.node_sigs
.values():
528 dct
.__delitem
__(node
.id)
530 # the policy is to avoid removing nodes representing directories
531 src_dir_node
.childs
.__delitem
__(node
.name
)
533 for variant
in self
.lst_variants
:
534 sub_path
= os
.path
.join(self
.bldnode
.abspath(), variant
, *lst
)
536 os
.makedirs(sub_path
)
540 # ======================================= #
541 def listdir_src(self
, parent_node
):
542 """do not use, kept for compatibility"""
545 def remove_node(self
, node
):
546 """do not use, kept for compatibility"""
549 def listdir_bld(self
, parent_node
, path
, variant
):
550 """in this method we do not add timestamps but we remove them
551 when the files no longer exist (file removed in the build dir)"""
553 i_existing_nodes
= [x
for x
in parent_node
.childs
.values() if x
.id & 3 == Node
.BUILD
]
555 lst
= set(Utils
.listdir(path
))
556 node_names
= set([x
.name
for x
in i_existing_nodes
])
557 remove_names
= node_names
- lst
559 # remove the stamps of the build nodes that no longer exist on the filesystem
560 ids_to_remove
= [x
.id for x
in i_existing_nodes
if x
.name
in remove_names
]
561 cache
= self
.node_sigs
[variant
]
562 for nid
in ids_to_remove
:
564 cache
.__delitem
__(nid
)
567 return self
.env_of_name('default')
568 def set_env(self
, name
, val
):
569 self
.all_envs
[name
] = val
571 env
= property(get_env
, set_env
)
573 def add_manual_dependency(self
, path
, value
):
574 if isinstance(path
, Node
.Node
):
576 elif os
.path
.isabs(path
):
577 node
= self
.root
.find_resource(path
)
579 node
= self
.path
.find_resource(path
)
580 self
.deps_man
[node
.id].append(value
)
582 def launch_node(self
):
583 """return the launch directory as a node"""
584 # p_ln is kind of private, but public in case if
587 except AttributeError:
588 self
.p_ln
= self
.root
.find_dir(Options
.launch_dir
)
591 def glob(self
, pattern
, relative
=True):
592 "files matching the pattern, seen from the current folder"
593 path
= self
.path
.abspath()
594 files
= [self
.root
.find_resource(x
) for x
in glob
.glob(path
+os
.sep
+pattern
)]
596 files
= [x
.path_to_parent(self
.path
) for x
in files
if x
]
598 files
= [x
.abspath() for x
in files
if x
]
601 ## the following methods are candidates for the stable apis ##
603 def add_group(self
, *k
):
604 self
.task_manager
.add_group(*k
)
606 def set_group(self
, *k
, **kw
):
607 self
.task_manager
.set_group(*k
, **kw
)
609 def hash_env_vars(self
, env
, vars_lst
):
610 """hash environment variables
611 ['CXX', ..] -> [env['CXX'], ..] -> md5()"""
613 # ccroot objects use the same environment for building the .o at once
614 # the same environment and the same variables are used
616 idx
= str(id(env
)) + str(vars_lst
)
617 try: return self
.cache_sig_vars
[idx
]
618 except KeyError: pass
620 lst
= [str(env
[a
]) for a
in vars_lst
]
621 ret
= Utils
.h_list(lst
)
622 debug('envhash: %r %r', ret
, lst
)
625 self
.cache_sig_vars
[idx
] = ret
628 def name_to_obj(self
, name
, env
):
629 """retrieve a task generator from its name or its target name
630 remember that names must be unique"""
631 cache
= self
.task_gen_cache_names
633 # create the index lazily
634 for x
in self
.all_task_gen
:
635 vt
= x
.env
.variant() + '_'
637 cache
[vt
+ x
.name
] = x
639 if isinstance(x
.target
, str):
642 target
= ' '.join(x
.target
)
644 if not cache
.get(v
, None):
646 return cache
.get(env
.variant() + '_' + name
, None)
648 def flush(self
, all
=1):
649 """tell the task generators to create the tasks"""
651 self
.ini
= datetime
.datetime
.now()
652 # force the initialization of the mapping name->object in flush
653 # name_to_obj can be used in userland scripts, in that case beware of incomplete mapping
654 self
.task_gen_cache_names
= {}
655 self
.name_to_obj('', self
.env
)
657 debug('build: delayed operation TaskGen.flush() called')
659 if Options
.options
.compile_targets
:
660 debug('task_gen: posting objects %r listed in compile_targets', Options
.options
.compile_targets
)
662 mana
= self
.task_manager
666 # ensure the target names exist, fail before any post()
667 target_objects
= Utils
.DefaultDict(list)
668 for target_name
in Options
.options
.compile_targets
.split(','):
669 # trim target_name (handle cases when the user added spaces to targets)
670 target_name
= target_name
.strip()
671 for env
in self
.all_envs
.values():
672 tg
= self
.name_to_obj(target_name
, env
)
674 target_objects
[target_name
].append(tg
)
676 m
= mana
.group_idx(tg
)
683 if not target_name
in target_objects
and all
:
684 raise Utils
.WafError("target '%s' does not exist" % target_name
)
686 debug('group: Forcing up to group %s for target %s', mana
.group_name(min_grp
), Options
.options
.compile_targets
)
688 # post all the task generators in previous groups
689 for i
in xrange(len(mana
.groups
)):
690 mana
.current_group
= i
694 debug('group: Forcing group %s', mana
.group_name(g
))
695 for t
in g
.tasks_gen
:
696 debug('group: Posting %s', t
.name
or t
.target
)
699 # then post the task generators listed in compile_targets in the last group
704 debug('task_gen: posting objects (normal)')
705 ln
= self
.launch_node()
706 # if the build is started from the build directory, do as if it was started from the top-level
707 # for the pretty-printing (Node.py), the two lines below cannot be moved to Build::launch_node
708 if ln
.is_child_of(self
.bldnode
) or not ln
.is_child_of(self
.srcnode
):
711 # if the project file is located under the source directory, build all targets by default
712 # else 'waf configure build' does nothing
713 proj_node
= self
.root
.find_dir(os
.path
.split(Utils
.g_module
.root_path
)[0])
714 if proj_node
.id != self
.srcnode
.id:
717 for i
in xrange(len(self
.task_manager
.groups
)):
718 g
= self
.task_manager
.groups
[i
]
719 self
.task_manager
.current_group
= i
721 groups
= [x
for x
in self
.task_manager
.groups_names
if id(self
.task_manager
.groups_names
[x
]) == id(g
)]
722 name
= groups
and groups
[0] or 'unnamed'
723 Logs
.debug('group: group', name
)
724 for tg
in g
.tasks_gen
:
725 if not tg
.path
.is_child_of(ln
):
728 Logs
.debug('group: %s' % tg
)
731 def env_of_name(self
, name
):
733 return self
.all_envs
[name
]
735 error('no such environment: '+name
)
738 def progress_line(self
, state
, total
, col1
, col2
):
742 ind
= Utils
.rot_chr
[Utils
.rot_idx
% 4]
746 pc
= (100.*state
)/total
747 eta
= Utils
.get_elapsed_time(ini
)
748 fs
= "[%%%dd/%%%dd][%%s%%2d%%%%%%s][%s][" % (n
, n
, ind
)
749 left
= fs
% (state
, total
, col1
, pc
, col2
)
750 right
= '][%s%s%s]' % (col1
, eta
, col2
)
752 cols
= Utils
.get_term_cols() - len(left
) - len(right
) + 2*len(col1
) + 2*len(col2
)
753 if cols
< 7: cols
= 7
755 ratio
= int((cols
*state
)/total
) - 1
757 bar
= ('='*ratio
+'>').ljust(cols
)
758 msg
= Utils
.indicator
% (left
, bar
, right
)
763 # do_install is not used anywhere
764 def do_install(self
, src
, tgt
, chmod
=O644
):
765 """returns true if the file was effectively installed or uninstalled, false otherwise"""
766 if self
.is_install
> 0:
767 if not Options
.options
.force
:
768 # check if the file is already there to avoid a copy
775 # same size and identical timestamps -> make no copy
776 if st1
.st_mtime
>= st2
.st_mtime
and st1
.st_size
== st2
.st_size
:
779 srclbl
= src
.replace(self
.srcnode
.abspath(None)+os
.sep
, '')
780 info("* installing %s as %s" % (srclbl
, tgt
))
782 # following is for shared libs and stale inodes (-_-)
787 shutil
.copy2(src
, tgt
)
792 except (OSError, IOError):
793 error('File %r does not exist' % src
)
794 raise Utils
.WafError('Could not install the file %r' % tgt
)
797 elif self
.is_install
< 0:
798 info("* uninstalling %s" % tgt
)
800 self
.uninstall
.append(tgt
)
805 if e
.errno
!= errno
.ENOENT
:
806 if not getattr(self
, 'uninstall_error', None):
807 self
.uninstall_error
= True
808 Logs
.warn('build: some files could not be uninstalled (retry with -vv to list them)')
810 Logs
.warn('could not remove %s (error code %r)' % (e
.filename
, e
.errno
))
813 red
= re
.compile(r
"^([A-Za-z]:)?[/\\\\]*")
814 def get_install_path(self
, path
, env
=None):
815 "installation path prefixed by the destdir, the variables like in '${PREFIX}/bin' are substituted"
816 if not env
: env
= self
.env
817 destdir
= env
.get_destdir()
818 path
= path
.replace('/', os
.sep
)
819 destpath
= Utils
.subst_vars(path
, env
)
821 destpath
= os
.path
.join(destdir
, self
.red
.sub('', destpath
))
824 def install_dir(self
, path
, env
=None):
826 create empty folders for the installation (very rarely used)
829 assert isinstance(env
, Environment
.Environment
), "invalid parameter"
836 destpath
= self
.get_install_path(path
, env
)
838 if self
.is_install
> 0:
839 info('* creating %s' % destpath
)
840 Utils
.check_dir(destpath
)
841 elif self
.is_install
< 0:
842 info('* removing %s' % destpath
)
843 self
.uninstall
.append(destpath
+ '/xxx') # yes, ugly
845 def install_files(self
, path
, files
, env
=None, chmod
=O644
, relative_trick
=False, cwd
=None):
846 """To install files only after they have been built, put the calls in a method named
847 post_build on the top-level wscript
849 The files must be a list and contain paths as strings or as Nodes
851 The relative_trick flag can be set to install folders, use bld.path.ant_glob() with it
854 assert isinstance(env
, Environment
.Environment
), "invalid parameter"
858 if not path
: return []
863 if isinstance(files
, str) and '*' in files
:
864 gl
= cwd
.abspath() + os
.sep
+ files
867 lst
= Utils
.to_list(files
)
869 if not getattr(lst
, '__iter__', False):
872 destpath
= self
.get_install_path(path
, env
)
874 Utils
.check_dir(destpath
)
878 if isinstance(filename
, str) and os
.path
.isabs(filename
):
879 alst
= Utils
.split_path(filename
)
880 destfile
= os
.path
.join(destpath
, alst
[-1])
882 if isinstance(filename
, Node
.Node
):
885 nd
= cwd
.find_resource(filename
)
887 raise Utils
.WafError("Unable to install the file %r (not found in %s)" % (filename
, cwd
))
890 destfile
= os
.path
.join(destpath
, filename
)
891 Utils
.check_dir(os
.path
.dirname(destfile
))
893 destfile
= os
.path
.join(destpath
, nd
.name
)
895 filename
= nd
.abspath(env
)
897 if self
.do_install(filename
, destfile
, chmod
):
898 installed_files
.append(destfile
)
899 return installed_files
901 def install_as(self
, path
, srcfile
, env
=None, chmod
=O644
, cwd
=None):
903 srcfile may be a string or a Node representing the file to install
905 returns True if the file was effectively installed, False otherwise
908 assert isinstance(env
, Environment
.Environment
), "invalid parameter"
913 raise Utils
.WafError("where do you want to install %r? (%r?)" % (srcfile
, path
))
918 destpath
= self
.get_install_path(path
, env
)
920 dir, name
= os
.path
.split(destpath
)
924 if isinstance(srcfile
, Node
.Node
):
925 src
= srcfile
.abspath(env
)
928 if not os
.path
.isabs(srcfile
):
929 node
= cwd
.find_resource(srcfile
)
931 raise Utils
.WafError("Unable to install the file %r (not found in %s)" % (srcfile
, cwd
))
932 src
= node
.abspath(env
)
934 return self
.do_install(src
, destpath
, chmod
)
936 def symlink_as(self
, path
, src
, env
=None, cwd
=None):
937 """example: bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3') """
939 if sys
.platform
== 'win32':
940 # well, this *cannot* work
944 raise Utils
.WafError("where do you want to install %r? (%r?)" % (src
, path
))
946 tgt
= self
.get_install_path(path
, env
)
948 dir, name
= os
.path
.split(tgt
)
951 if self
.is_install
> 0:
953 if not os
.path
.islink(tgt
):
955 elif os
.readlink(tgt
) != src
:
962 info('* symlink %s (-> %s)' % (tgt
, src
))
968 info('* removing %s' % (tgt
))
974 def exec_command(self
, cmd
, **kw
):
975 # 'runner' zone is printed out for waf -v, see wafadmin/Options.py
976 debug('runner: system command -> %s', cmd
)
978 self
.log
.write('%s\n' % cmd
)
981 if not kw
.get('cwd', None):
983 except AttributeError:
984 self
.cwd
= kw
['cwd'] = self
.bldnode
.abspath()
985 return Utils
.exec_command(cmd
, **kw
)
987 def printout(self
, s
):
988 f
= self
.log
or sys
.stderr
992 def add_subdirs(self
, dirs
):
993 self
.recurse(dirs
, 'build')
995 def pre_recurse(self
, name_or_mod
, path
, nexdir
):
996 if not hasattr(self
, 'oldpath'):
998 self
.oldpath
.append(self
.path
)
999 self
.path
= self
.root
.find_dir(nexdir
)
1000 return {'bld': self
, 'ctx': self
}
1002 def post_recurse(self
, name_or_mod
, path
, nexdir
):
1003 self
.path
= self
.oldpath
.pop()
1005 ###### user-defined behaviour
1007 def pre_build(self
):
1008 if hasattr(self
, 'pre_funs'):
1009 for m
in self
.pre_funs
:
1012 def post_build(self
):
1013 if hasattr(self
, 'post_funs'):
1014 for m
in self
.post_funs
:
1017 def add_pre_fun(self
, meth
):
1018 try: self
.pre_funs
.append(meth
)
1019 except AttributeError: self
.pre_funs
= [meth
]
1021 def add_post_fun(self
, meth
):
1022 try: self
.post_funs
.append(meth
)
1023 except AttributeError: self
.post_funs
= [meth
]
1025 def use_the_magic(self
):
1026 Task
.algotype
= Task
.MAXPARALLEL
1027 Task
.file_deps
= Task
.extract_deps
1030 install_as
= group_method(install_as
)
1031 install_files
= group_method(install_files
)
1032 symlink_as
= group_method(symlink_as
)