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 if isinstance(default_cmd
, list):
220 Options
.commands
.extend(default_cmd
)
222 Options
.commands
.append(default_cmd
)
223 if Options
.options
.whelp
:
224 ctx
.parser
.print_help()
227 def run_command(cmd_name
):
229 Executes a single Waf command. Called by :py:func:`waflib.Scripting.run_commands`.
231 :param cmd_name: command to execute, like ``build``
232 :type cmd_name: string
234 ctx
= Context
.create_context(cmd_name
)
235 ctx
.log_timer
= Utils
.Timer()
236 ctx
.options
= Options
.options
# provided for convenience
247 Execute the Waf commands that were given on the command-line, and the other options
248 Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization, and executed
249 after :py:func:`waflib.Scripting.parse_options`.
253 while Options
.commands
:
254 cmd_name
= Options
.commands
.pop(0)
255 ctx
= run_command(cmd_name
)
256 Logs
.info('%r finished successfully (%s)', cmd_name
, ctx
.log_timer
)
257 run_command('shutdown')
259 ###########################################################################################
261 def distclean_dir(dirname
):
263 Distclean function called in the particular case when::
267 :param dirname: absolute path of the folder to clean
268 :type dirname: string
270 for (root
, dirs
, files
) in os
.walk(dirname
):
272 if f
.endswith(('.o', '.moc', '.exe')):
273 fname
= os
.path
.join(root
, f
)
277 Logs
.warn('Could not remove %r', fname
)
279 for x
in (Context
.DBFILE
, 'config.log'):
286 shutil
.rmtree(Build
.CACHE_DIR
)
291 '''removes build folders and data'''
293 def remove_and_log(k
, fun
):
296 except EnvironmentError as e
:
297 if e
.errno
!= errno
.ENOENT
:
298 Logs
.warn('Could not remove %r', k
)
300 # remove waf cache folders on the top-level
301 if not Options
.commands
:
302 for k
in os
.listdir('.'):
303 for x
in '.waf-2 waf-2 .waf3-2 waf3-2'.split():
305 remove_and_log(k
, shutil
.rmtree
)
307 # remove a build folder, if any
309 if os
.environ
.get('NO_LOCK_IN_TOP') or ctx
.options
.no_lock_in_top
:
310 cur
= ctx
.options
.out
313 lst
= os
.listdir(cur
)
315 Logs
.warn('Could not read %r', cur
)
318 if Options
.lockfile
in lst
:
319 f
= os
.path
.join(cur
, Options
.lockfile
)
321 env
= ConfigSet
.ConfigSet(f
)
322 except EnvironmentError:
323 Logs
.warn('Could not read %r', f
)
326 if not env
.out_dir
or not env
.top_dir
:
327 Logs
.warn('Invalid lock file %r', f
)
330 if env
.out_dir
== env
.top_dir
:
331 distclean_dir(env
.out_dir
)
333 remove_and_log(env
.out_dir
, shutil
.rmtree
)
335 env_dirs
= [env
.out_dir
]
336 if not (os
.environ
.get('NO_LOCK_IN_TOP') or ctx
.options
.no_lock_in_top
):
337 env_dirs
.append(env
.top_dir
)
338 if not (os
.environ
.get('NO_LOCK_IN_RUN') or ctx
.options
.no_lock_in_run
):
339 env_dirs
.append(env
.run_dir
)
341 p
= os
.path
.join(k
, Options
.lockfile
)
342 remove_and_log(p
, os
.remove
)
344 class Dist(Context
.Context
):
345 '''creates an archive containing the project source code'''
353 See :py:func:`waflib.Context.Context.execute`
355 self
.recurse([os
.path
.dirname(Context
.g_module
.root_path
)])
360 Creates the source archive.
364 arch_name
= self
.get_arch_name()
368 except AttributeError:
369 self
.base_path
= self
.path
371 node
= self
.base_path
.make_node(arch_name
)
377 files
= self
.get_files()
379 if self
.algo
.startswith('tar.'):
380 tar
= tarfile
.open(node
.abspath(), 'w:' + self
.algo
.replace('tar.', ''))
383 self
.add_tar_file(x
, tar
)
385 elif self
.algo
== 'zip':
387 zip = zipfile
.ZipFile(node
.abspath(), 'w', compression
=zipfile
.ZIP_DEFLATED
)
390 archive_name
= self
.get_base_name() + '/' + x
.path_from(self
.base_path
)
391 if os
.environ
.get('SOURCE_DATE_EPOCH'):
392 # TODO: parse that timestamp
393 zip.writestr(zipfile
.ZipInfo(archive_name
), x
.read(), zipfile
.ZIP_DEFLATED
)
395 zip.write(x
.abspath(), archive_name
, zipfile
.ZIP_DEFLATED
)
398 self
.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
401 from hashlib
import sha256
405 digest
= ' (sha256=%r)' % sha256(node
.read(flags
='rb')).hexdigest()
407 Logs
.info('New archive created: %s%s', self
.arch_name
, digest
)
409 def get_tar_path(self
, node
):
411 Return the path to use for a node in the tar archive, the purpose of this
412 is to let subclases resolve symbolic links or to change file names
414 :return: absolute path
417 return node
.abspath()
419 def add_tar_file(self
, x
, tar
):
421 Adds a file to the tar archive. Symlinks are not verified.
424 :param tar: tar file object
426 p
= self
.get_tar_path(x
)
427 tinfo
= tar
.gettarinfo(name
=p
, arcname
=self
.get_tar_prefix() + '/' + x
.path_from(self
.base_path
))
432 if os
.environ
.get('SOURCE_DATE_EPOCH'):
433 tinfo
.mtime
= int(os
.environ
.get('SOURCE_DATE_EPOCH'))
435 if os
.path
.isfile(p
):
436 with
open(p
, 'rb') as f
:
437 tar
.addfile(tinfo
, fileobj
=f
)
441 def get_tar_prefix(self
):
443 Returns the base path for files added into the archive tar file
448 return self
.tar_prefix
449 except AttributeError:
450 return self
.get_base_name()
452 def get_arch_name(self
):
454 Returns the archive file name.
455 Set the attribute *arch_name* to change the default value::
458 ctx.arch_name = 'ctx.tar.bz2'
464 except AttributeError:
465 self
.arch_name
= self
.get_base_name() + '.' + self
.ext_algo
.get(self
.algo
, self
.algo
)
466 return self
.arch_name
468 def get_base_name(self
):
470 Returns the default name of the main directory in the archive, which is set to *appname-version*.
471 Set the attribute *base_name* to change the default value::
474 ctx.base_name = 'files'
480 except AttributeError:
481 appname
= getattr(Context
.g_module
, Context
.APPNAME
, 'noname')
482 version
= getattr(Context
.g_module
, Context
.VERSION
, '1.0')
483 self
.base_name
= appname
+ '-' + version
484 return self
.base_name
488 Returns the patterns to exclude for finding the files in the top-level directory.
489 Set the attribute *excl* to change the default value::
492 ctx.excl = 'build **/*.o **/*.class'
498 except AttributeError:
499 self
.excl
= Node
.exclude_regs
+ ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
501 nd
= self
.root
.find_node(Context
.out_dir
)
503 self
.excl
+= ' ' + nd
.path_from(self
.base_path
)
508 Files to package are searched automatically by :py:func:`waflib.Node.Node.ant_glob`.
509 Set *files* to prevent this behaviour::
512 ctx.files = ctx.path.find_node('wscript')
514 Files are also searched from the directory 'base_path', to change it, set::
519 :rtype: list of :py:class:`waflib.Node.Node`
523 except AttributeError:
524 files
= self
.base_path
.ant_glob('**/*', excl
=self
.get_excl())
528 '''makes a tarball for redistributing the sources'''
531 class DistCheck(Dist
):
532 """creates an archive with dist, then tries to build it"""
538 See :py:func:`waflib.Context.Context.execute`
540 self
.recurse([os
.path
.dirname(Context
.g_module
.root_path
)])
544 def make_distcheck_cmd(self
, tmpdir
):
546 if Options
.options
.distcheck_args
:
547 cfg
= shlex
.split(Options
.options
.distcheck_args
)
549 cfg
= [x
for x
in sys
.argv
if x
.startswith('-')]
550 cmd
= [sys
.executable
, sys
.argv
[0], 'configure', 'build', 'install', 'uninstall', '--destdir=' + tmpdir
] + cfg
555 Creates the archive, uncompresses it and tries to build the project
557 import tempfile
, tarfile
559 with tarfile
.open(self
.get_arch_name()) as t
:
563 instdir
= tempfile
.mkdtemp('.inst', self
.get_base_name())
564 cmd
= self
.make_distcheck_cmd(instdir
)
565 ret
= Utils
.subprocess
.Popen(cmd
, cwd
=self
.get_base_name()).wait()
567 raise Errors
.WafError('distcheck failed with code %r' % ret
)
569 if os
.path
.exists(instdir
):
570 raise Errors
.WafError('distcheck succeeded, but files were left in %s' % instdir
)
572 shutil
.rmtree(self
.get_base_name())
576 '''checks if the project compiles (tarball from 'dist')'''
579 def autoconfigure(execute_method
):
581 Decorator that enables context commands to run *configure* as needed.
585 Wraps :py:func:`waflib.Context.Context.execute` on the context class
587 if not Configure
.autoconfig
:
588 return execute_method(self
)
590 env
= ConfigSet
.ConfigSet()
593 env
.load(os
.path
.join(Context
.top_dir
, Options
.lockfile
))
594 except EnvironmentError:
595 Logs
.warn('Configuring the project')
598 if env
.run_dir
!= Context
.run_dir
:
604 h
= Utils
.h_list((h
, Utils
.readf(f
, 'rb')))
605 except EnvironmentError:
609 do_config
= h
!= env
.hash
612 cmd
= env
.config_cmd
or 'configure'
613 if Configure
.autoconfig
== 'clobber':
614 tmp
= Options
.options
.__dict
__
615 launch_dir_tmp
= Context
.launch_dir
617 Options
.options
.__dict
__ = env
.options
618 Context
.launch_dir
= env
.launch_dir
622 Options
.options
.__dict
__ = tmp
623 Context
.launch_dir
= launch_dir_tmp
626 run_command(self
.cmd
)
628 return execute_method(self
)
630 Build
.BuildContext
.execute
= autoconfigure(Build
.BuildContext
.execute
)