3 # Thomas Nagy, 2005-2018 (ita)
5 "Module called for configuring, compiling and installing targets"
7 from __future__
import with_statement
9 import os
, shlex
, shutil
, traceback
, errno
, sys
, stat
10 from waflib
import Utils
, Configure
, Logs
, Options
, ConfigSet
, Context
, Errors
, Build
, Node
12 build_dir_override
= None
14 no_climb_commands
= ['configure']
18 def waf_entry_point(current_directory
, version
, wafdir
):
20 This is the main entry point, all Waf execution starts here.
22 :param current_directory: absolute path representing the current directory
23 :type current_directory: string
24 :param version: version number
26 :param wafdir: absolute path representing the directory of the waf library
31 if Context
.WAFVERSION
!= version
:
32 Logs
.error('Waf script %r and library %r do not match (directory %r)', version
, Context
.WAFVERSION
, wafdir
)
35 # Store current directory before any chdir
36 Context
.waf_dir
= wafdir
37 Context
.run_dir
= Context
.launch_dir
= current_directory
38 start_dir
= current_directory
39 no_climb
= os
.environ
.get('NOCLIMB')
42 # os.path.join handles absolute paths
43 # if sys.argv[1] is not an absolute path, then it is relative to the current working directory
44 potential_wscript
= os
.path
.join(current_directory
, sys
.argv
[1])
45 if os
.path
.basename(potential_wscript
) == Context
.WSCRIPT_FILE
and os
.path
.isfile(potential_wscript
):
46 # need to explicitly normalize the path, as it may contain extra '/.'
47 path
= os
.path
.normpath(os
.path
.dirname(potential_wscript
))
48 start_dir
= os
.path
.abspath(path
)
52 ctx
= Context
.create_context('options')
53 (options
, commands
, env
) = ctx
.parse_cmd_args(allow_unknown
=True)
55 start_dir
= Context
.run_dir
= Context
.top_dir
= options
.top
58 Context
.out_dir
= options
.out
60 # if 'configure' is in the commands, do not search any further
62 for k
in no_climb_commands
:
68 # try to find a lock file (if the project was configured)
69 # at the same time, store the first wscript file seen
76 Logs
.error('Directory %r is unreadable!', cur
)
77 if Options
.lockfile
in lst
:
78 env
= ConfigSet
.ConfigSet()
80 env
.load(os
.path
.join(cur
, Options
.lockfile
))
81 ino
= os
.stat(cur
)[stat
.ST_INO
]
82 except EnvironmentError:
85 # check if the folder was not moved
86 for x
in (env
.run_dir
, env
.top_dir
, env
.out_dir
):
94 # if the filesystem features symlinks, compare the inode numbers
96 ino2
= os
.stat(x
)[stat
.ST_INO
]
104 Logs
.warn('invalid lock file in %s', cur
)
108 Context
.run_dir
= env
.run_dir
109 Context
.top_dir
= env
.top_dir
110 Context
.out_dir
= env
.out_dir
113 if not Context
.run_dir
:
114 if Context
.WSCRIPT_FILE
in lst
:
115 Context
.run_dir
= cur
117 next
= os
.path
.dirname(cur
)
125 wscript
= os
.path
.normpath(os
.path
.join(Context
.run_dir
, Context
.WSCRIPT_FILE
))
126 if not os
.path
.exists(wscript
):
128 Logs
.warn('These are the generic options (no wscript/project found)')
129 ctx
.parser
.print_help()
131 Logs
.error('Waf: Run from a folder containing a %r file (or try -h for the generic options)', Context
.WSCRIPT_FILE
)
135 os
.chdir(Context
.run_dir
)
137 Logs
.error('Waf: The folder %r is unreadable', Context
.run_dir
)
141 set_main_module(wscript
)
142 except Errors
.WafError
as e
:
143 Logs
.pprint('RED', e
.verbose_msg
)
146 except Exception as e
:
147 Logs
.error('Waf: The wscript in %r is unreadable', Context
.run_dir
)
148 traceback
.print_exc(file=sys
.stdout
)
152 import cProfile
, pstats
153 cProfile
.runctx('from waflib import Scripting; Scripting.run_commands()', {}, {}, 'profi.txt')
154 p
= pstats
.Stats('profi.txt')
155 p
.sort_stats('time').print_stats(75) # or 'cumulative'
163 type, value
, tb
= sys
.exc_info()
164 traceback
.print_exc()
168 except Errors
.WafError
as e
:
170 Logs
.pprint('RED', e
.verbose_msg
)
175 except Exception as e
:
176 traceback
.print_exc(file=sys
.stdout
)
178 except KeyboardInterrupt:
179 Logs
.pprint('RED', 'Interrupted')
182 def set_main_module(file_path
):
184 Read the main wscript file into :py:const:`waflib.Context.Context.g_module` and
185 bind default functions such as ``init``, ``dist``, ``distclean`` if not defined.
186 Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
188 :param file_path: absolute path representing the top-level wscript file
189 :type file_path: string
191 Context
.g_module
= Context
.load_module(file_path
)
192 Context
.g_module
.root_path
= file_path
194 # note: to register the module globally, use the following:
195 # sys.modules['wscript_main'] = g_module
199 if not name
in Context
.g_module
.__dict
__:
200 setattr(Context
.g_module
, name
, obj
)
201 for k
in (dist
, distclean
, distcheck
):
203 # add dummy init and shutdown functions if they're not defined
204 if not 'init' in Context
.g_module
.__dict
__:
205 Context
.g_module
.init
= Utils
.nada
206 if not 'shutdown' in Context
.g_module
.__dict
__:
207 Context
.g_module
.shutdown
= Utils
.nada
208 if not 'options' in Context
.g_module
.__dict
__:
209 Context
.g_module
.options
= Utils
.nada
213 Parses the command-line options and initialize the logging system.
214 Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
216 ctx
= Context
.create_context('options')
218 if not Options
.commands
:
219 Options
.commands
.append(default_cmd
)
220 if Options
.options
.whelp
:
221 ctx
.parser
.print_help()
224 def run_command(cmd_name
):
226 Executes a single Waf command. Called by :py:func:`waflib.Scripting.run_commands`.
228 :param cmd_name: command to execute, like ``build``
229 :type cmd_name: string
231 ctx
= Context
.create_context(cmd_name
)
232 ctx
.log_timer
= Utils
.Timer()
233 ctx
.options
= Options
.options
# provided for convenience
244 Execute the Waf commands that were given on the command-line, and the other options
245 Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization, and executed
246 after :py:func:`waflib.Scripting.parse_options`.
250 while Options
.commands
:
251 cmd_name
= Options
.commands
.pop(0)
252 ctx
= run_command(cmd_name
)
253 Logs
.info('%r finished successfully (%s)', cmd_name
, ctx
.log_timer
)
254 run_command('shutdown')
256 ###########################################################################################
258 def distclean_dir(dirname
):
260 Distclean function called in the particular case when::
264 :param dirname: absolute path of the folder to clean
265 :type dirname: string
267 for (root
, dirs
, files
) in os
.walk(dirname
):
269 if f
.endswith(('.o', '.moc', '.exe')):
270 fname
= os
.path
.join(root
, f
)
274 Logs
.warn('Could not remove %r', fname
)
276 for x
in (Context
.DBFILE
, 'config.log'):
283 shutil
.rmtree('c4che')
288 '''removes build folders and data'''
290 def remove_and_log(k
, fun
):
293 except EnvironmentError as e
:
294 if e
.errno
!= errno
.ENOENT
:
295 Logs
.warn('Could not remove %r', k
)
297 # remove waf cache folders on the top-level
298 if not Options
.commands
:
299 for k
in os
.listdir('.'):
300 for x
in '.waf-2 waf-2 .waf3-2 waf3-2'.split():
302 remove_and_log(k
, shutil
.rmtree
)
304 # remove a build folder, if any
306 if ctx
.options
.no_lock_in_top
:
307 cur
= ctx
.options
.out
310 lst
= os
.listdir(cur
)
312 Logs
.warn('Could not read %r', cur
)
315 if Options
.lockfile
in lst
:
316 f
= os
.path
.join(cur
, Options
.lockfile
)
318 env
= ConfigSet
.ConfigSet(f
)
319 except EnvironmentError:
320 Logs
.warn('Could not read %r', f
)
323 if not env
.out_dir
or not env
.top_dir
:
324 Logs
.warn('Invalid lock file %r', f
)
327 if env
.out_dir
== env
.top_dir
:
328 distclean_dir(env
.out_dir
)
330 remove_and_log(env
.out_dir
, shutil
.rmtree
)
332 for k
in (env
.out_dir
, env
.top_dir
, env
.run_dir
):
333 p
= os
.path
.join(k
, Options
.lockfile
)
334 remove_and_log(p
, os
.remove
)
336 class Dist(Context
.Context
):
337 '''creates an archive containing the project source code'''
345 See :py:func:`waflib.Context.Context.execute`
347 self
.recurse([os
.path
.dirname(Context
.g_module
.root_path
)])
352 Creates the source archive.
356 arch_name
= self
.get_arch_name()
360 except AttributeError:
361 self
.base_path
= self
.path
363 node
= self
.base_path
.make_node(arch_name
)
369 files
= self
.get_files()
371 if self
.algo
.startswith('tar.'):
372 tar
= tarfile
.open(node
.abspath(), 'w:' + self
.algo
.replace('tar.', ''))
375 self
.add_tar_file(x
, tar
)
377 elif self
.algo
== 'zip':
379 zip = zipfile
.ZipFile(node
.abspath(), 'w', compression
=zipfile
.ZIP_DEFLATED
)
382 archive_name
= self
.get_base_name() + '/' + x
.path_from(self
.base_path
)
383 zip.write(x
.abspath(), archive_name
, zipfile
.ZIP_DEFLATED
)
386 self
.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
389 from hashlib
import sha256
393 digest
= ' (sha256=%r)' % sha256(node
.read(flags
='rb')).hexdigest()
395 Logs
.info('New archive created: %s%s', self
.arch_name
, digest
)
397 def get_tar_path(self
, node
):
399 Return the path to use for a node in the tar archive, the purpose of this
400 is to let subclases resolve symbolic links or to change file names
402 :return: absolute path
405 return node
.abspath()
407 def add_tar_file(self
, x
, tar
):
409 Adds a file to the tar archive. Symlinks are not verified.
412 :param tar: tar file object
414 p
= self
.get_tar_path(x
)
415 tinfo
= tar
.gettarinfo(name
=p
, arcname
=self
.get_tar_prefix() + '/' + x
.path_from(self
.base_path
))
421 if os
.path
.isfile(p
):
422 with
open(p
, 'rb') as f
:
423 tar
.addfile(tinfo
, fileobj
=f
)
427 def get_tar_prefix(self
):
429 Returns the base path for files added into the archive tar file
434 return self
.tar_prefix
435 except AttributeError:
436 return self
.get_base_name()
438 def get_arch_name(self
):
440 Returns the archive file name.
441 Set the attribute *arch_name* to change the default value::
444 ctx.arch_name = 'ctx.tar.bz2'
450 except AttributeError:
451 self
.arch_name
= self
.get_base_name() + '.' + self
.ext_algo
.get(self
.algo
, self
.algo
)
452 return self
.arch_name
454 def get_base_name(self
):
456 Returns the default name of the main directory in the archive, which is set to *appname-version*.
457 Set the attribute *base_name* to change the default value::
460 ctx.base_name = 'files'
466 except AttributeError:
467 appname
= getattr(Context
.g_module
, Context
.APPNAME
, 'noname')
468 version
= getattr(Context
.g_module
, Context
.VERSION
, '1.0')
469 self
.base_name
= appname
+ '-' + version
470 return self
.base_name
474 Returns the patterns to exclude for finding the files in the top-level directory.
475 Set the attribute *excl* to change the default value::
478 ctx.excl = 'build **/*.o **/*.class'
484 except AttributeError:
485 self
.excl
= Node
.exclude_regs
+ ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
487 nd
= self
.root
.find_node(Context
.out_dir
)
489 self
.excl
+= ' ' + nd
.path_from(self
.base_path
)
494 Files to package are searched automatically by :py:func:`waflib.Node.Node.ant_glob`.
495 Set *files* to prevent this behaviour::
498 ctx.files = ctx.path.find_node('wscript')
500 Files are also searched from the directory 'base_path', to change it, set::
505 :rtype: list of :py:class:`waflib.Node.Node`
509 except AttributeError:
510 files
= self
.base_path
.ant_glob('**/*', excl
=self
.get_excl())
514 '''makes a tarball for redistributing the sources'''
517 class DistCheck(Dist
):
518 """creates an archive with dist, then tries to build it"""
524 See :py:func:`waflib.Context.Context.execute`
526 self
.recurse([os
.path
.dirname(Context
.g_module
.root_path
)])
530 def make_distcheck_cmd(self
, tmpdir
):
532 if Options
.options
.distcheck_args
:
533 cfg
= shlex
.split(Options
.options
.distcheck_args
)
535 cfg
= [x
for x
in sys
.argv
if x
.startswith('-')]
536 cmd
= [sys
.executable
, sys
.argv
[0], 'configure', 'build', 'install', 'uninstall', '--destdir=' + tmpdir
] + cfg
541 Creates the archive, uncompresses it and tries to build the project
543 import tempfile
, tarfile
545 with tarfile
.open(self
.get_arch_name()) as t
:
549 instdir
= tempfile
.mkdtemp('.inst', self
.get_base_name())
550 cmd
= self
.make_distcheck_cmd(instdir
)
551 ret
= Utils
.subprocess
.Popen(cmd
, cwd
=self
.get_base_name()).wait()
553 raise Errors
.WafError('distcheck failed with code %r' % ret
)
555 if os
.path
.exists(instdir
):
556 raise Errors
.WafError('distcheck succeeded, but files were left in %s' % instdir
)
558 shutil
.rmtree(self
.get_base_name())
562 '''checks if the project compiles (tarball from 'dist')'''
565 def autoconfigure(execute_method
):
567 Decorator that enables context commands to run *configure* as needed.
571 Wraps :py:func:`waflib.Context.Context.execute` on the context class
573 if not Configure
.autoconfig
:
574 return execute_method(self
)
576 env
= ConfigSet
.ConfigSet()
579 env
.load(os
.path
.join(Context
.top_dir
, Options
.lockfile
))
580 except EnvironmentError:
581 Logs
.warn('Configuring the project')
584 if env
.run_dir
!= Context
.run_dir
:
590 h
= Utils
.h_list((h
, Utils
.readf(f
, 'rb')))
591 except EnvironmentError:
595 do_config
= h
!= env
.hash
598 cmd
= env
.config_cmd
or 'configure'
599 if Configure
.autoconfig
== 'clobber':
600 tmp
= Options
.options
.__dict
__
602 Options
.options
.__dict
__ = env
.options
606 Options
.options
.__dict
__ = tmp
609 run_command(self
.cmd
)
611 return execute_method(self
)
613 Build
.BuildContext
.execute
= autoconfigure(Build
.BuildContext
.execute
)