2016-03-26 13:32:11 +01:00
#!/usr/bin/env python
# encoding: utf-8
2018-01-31 11:48:43 +02:00
# Thomas Nagy, 2005-2018 (ita)
2016-03-26 13:32:11 +01:00
" Module called for configuring, compiling and installing targets "
2018-01-31 11:48:43 +02:00
from __future__ import with_statement
2016-03-26 13:32:11 +01:00
import os , shlex , shutil , traceback , errno , sys , stat
from waflib import Utils , Configure , Logs , Options , ConfigSet , Context , Errors , Build , Node
build_dir_override = None
no_climb_commands = [ ' configure ' ]
default_cmd = " build "
def waf_entry_point ( current_directory , version , wafdir ) :
"""
This is the main entry point , all Waf execution starts here .
: param current_directory : absolute path representing the current directory
: type current_directory : string
: param version : version number
: type version : string
: param wafdir : absolute path representing the directory of the waf library
: type wafdir : string
"""
Logs . init_log ( )
if Context . WAFVERSION != version :
Logs . error ( ' Waf script %r and library %r do not match (directory %r ) ' , version , Context . WAFVERSION , wafdir )
sys . exit ( 1 )
2018-01-31 11:48:43 +02:00
# Store current directory before any chdir
Context . waf_dir = wafdir
Context . run_dir = Context . launch_dir = current_directory
start_dir = current_directory
no_climb = os . environ . get ( ' NOCLIMB ' )
2016-03-26 13:32:11 +01:00
if len ( sys . argv ) > 1 :
2018-01-31 11:48:43 +02:00
# os.path.join handles absolute paths
2016-03-26 13:32:11 +01:00
# if sys.argv[1] is not an absolute path, then it is relative to the current working directory
potential_wscript = os . path . join ( current_directory , sys . argv [ 1 ] )
2018-01-31 11:48:43 +02:00
if os . path . basename ( potential_wscript ) == Context . WSCRIPT_FILE and os . path . isfile ( potential_wscript ) :
2016-03-26 13:32:11 +01:00
# need to explicitly normalize the path, as it may contain extra '/.'
2018-01-31 11:48:43 +02:00
path = os . path . normpath ( os . path . dirname ( potential_wscript ) )
start_dir = os . path . abspath ( path )
no_climb = True
2016-03-26 13:32:11 +01:00
sys . argv . pop ( 1 )
2018-01-31 11:48:43 +02:00
ctx = Context . create_context ( ' options ' )
( options , commands , env ) = ctx . parse_cmd_args ( allow_unknown = True )
if options . top :
start_dir = Context . run_dir = Context . top_dir = options . top
no_climb = True
if options . out :
Context . out_dir = options . out
2016-03-26 13:32:11 +01:00
# if 'configure' is in the commands, do not search any further
if not no_climb :
for k in no_climb_commands :
2018-01-31 11:48:43 +02:00
for y in commands :
2016-03-26 13:32:11 +01:00
if y . startswith ( k ) :
no_climb = True
break
# try to find a lock file (if the project was configured)
# at the same time, store the first wscript file seen
2018-01-31 11:48:43 +02:00
cur = start_dir
while cur :
2016-03-26 13:32:11 +01:00
try :
lst = os . listdir ( cur )
except OSError :
lst = [ ]
Logs . error ( ' Directory %r is unreadable! ' , cur )
if Options . lockfile in lst :
env = ConfigSet . ConfigSet ( )
try :
env . load ( os . path . join ( cur , Options . lockfile ) )
ino = os . stat ( cur ) [ stat . ST_INO ]
except EnvironmentError :
pass
else :
# check if the folder was not moved
for x in ( env . run_dir , env . top_dir , env . out_dir ) :
if not x :
continue
if Utils . is_win32 :
if cur == x :
load = True
break
else :
# if the filesystem features symlinks, compare the inode numbers
try :
ino2 = os . stat ( x ) [ stat . ST_INO ]
except OSError :
pass
else :
if ino == ino2 :
load = True
break
else :
Logs . warn ( ' invalid lock file in %s ' , cur )
load = False
if load :
Context . run_dir = env . run_dir
Context . top_dir = env . top_dir
Context . out_dir = env . out_dir
break
if not Context . run_dir :
if Context . WSCRIPT_FILE in lst :
Context . run_dir = cur
next = os . path . dirname ( cur )
if next == cur :
break
cur = next
if no_climb :
break
if not Context . run_dir :
2018-01-31 11:48:43 +02:00
if options . whelp :
Logs . warn ( ' These are the generic options (no wscript/project found) ' )
ctx . parser . print_help ( )
2016-03-26 13:32:11 +01:00
sys . exit ( 0 )
2018-01-31 11:48:43 +02:00
Logs . error ( ' Waf: Run from a folder containing a %r file (or try -h for the generic options) ' , Context . WSCRIPT_FILE )
2016-03-26 13:32:11 +01:00
sys . exit ( 1 )
try :
os . chdir ( Context . run_dir )
except OSError :
Logs . error ( ' Waf: The folder %r is unreadable ' , Context . run_dir )
sys . exit ( 1 )
try :
set_main_module ( os . path . normpath ( os . path . join ( Context . run_dir , Context . WSCRIPT_FILE ) ) )
2018-01-31 11:48:43 +02:00
except Errors . WafError as e :
2016-03-26 13:32:11 +01:00
Logs . pprint ( ' RED ' , e . verbose_msg )
Logs . error ( str ( e ) )
sys . exit ( 1 )
2018-01-31 11:48:43 +02:00
except Exception as e :
2016-03-26 13:32:11 +01:00
Logs . error ( ' Waf: The wscript in %r is unreadable ' , Context . run_dir )
traceback . print_exc ( file = sys . stdout )
sys . exit ( 2 )
2018-01-31 11:48:43 +02:00
if options . profile :
2016-03-26 13:32:11 +01:00
import cProfile , pstats
cProfile . runctx ( ' from waflib import Scripting; Scripting.run_commands() ' , { } , { } , ' profi.txt ' )
p = pstats . Stats ( ' profi.txt ' )
p . sort_stats ( ' time ' ) . print_stats ( 75 ) # or 'cumulative'
else :
try :
2018-01-31 11:48:43 +02:00
try :
run_commands ( )
except :
if options . pdb :
import pdb
type , value , tb = sys . exc_info ( )
traceback . print_exc ( )
pdb . post_mortem ( tb )
else :
raise
except Errors . WafError as e :
2016-03-26 13:32:11 +01:00
if Logs . verbose > 1 :
Logs . pprint ( ' RED ' , e . verbose_msg )
Logs . error ( e . msg )
sys . exit ( 1 )
except SystemExit :
raise
2018-01-31 11:48:43 +02:00
except Exception as e :
2016-03-26 13:32:11 +01:00
traceback . print_exc ( file = sys . stdout )
sys . exit ( 2 )
except KeyboardInterrupt :
Logs . pprint ( ' RED ' , ' Interrupted ' )
sys . exit ( 68 )
def set_main_module ( file_path ) :
"""
Read the main wscript file into : py : const : ` waflib . Context . Context . g_module ` and
bind default functions such as ` ` init ` ` , ` ` dist ` ` , ` ` distclean ` ` if not defined .
Called by : py : func : ` waflib . Scripting . waf_entry_point ` during the initialization .
: param file_path : absolute path representing the top - level wscript file
: type file_path : string
"""
Context . g_module = Context . load_module ( file_path )
Context . g_module . root_path = file_path
# note: to register the module globally, use the following:
# sys.modules['wscript_main'] = g_module
def set_def ( obj ) :
name = obj . __name__
if not name in Context . g_module . __dict__ :
setattr ( Context . g_module , name , obj )
for k in ( dist , distclean , distcheck ) :
set_def ( k )
# add dummy init and shutdown functions if they're not defined
if not ' init ' in Context . g_module . __dict__ :
Context . g_module . init = Utils . nada
if not ' shutdown ' in Context . g_module . __dict__ :
Context . g_module . shutdown = Utils . nada
if not ' options ' in Context . g_module . __dict__ :
Context . g_module . options = Utils . nada
def parse_options ( ) :
"""
Parses the command - line options and initialize the logging system .
Called by : py : func : ` waflib . Scripting . waf_entry_point ` during the initialization .
"""
2018-01-31 11:48:43 +02:00
ctx = Context . create_context ( ' options ' )
ctx . execute ( )
2016-03-26 13:32:11 +01:00
if not Options . commands :
2018-01-31 11:48:43 +02:00
Options . commands . append ( default_cmd )
if Options . options . whelp :
ctx . parser . print_help ( )
sys . exit ( 0 )
2016-03-26 13:32:11 +01:00
def run_command ( cmd_name ) :
"""
Executes a single Waf command . Called by : py : func : ` waflib . Scripting . run_commands ` .
: param cmd_name : command to execute , like ` ` build ` `
: type cmd_name : string
"""
ctx = Context . create_context ( cmd_name )
ctx . log_timer = Utils . Timer ( )
ctx . options = Options . options # provided for convenience
ctx . cmd = cmd_name
try :
ctx . execute ( )
finally :
# Issue 1374
ctx . finalize ( )
return ctx
def run_commands ( ) :
"""
Execute the Waf commands that were given on the command - line , and the other options
Called by : py : func : ` waflib . Scripting . waf_entry_point ` during the initialization , and executed
after : py : func : ` waflib . Scripting . parse_options ` .
"""
parse_options ( )
run_command ( ' init ' )
while Options . commands :
cmd_name = Options . commands . pop ( 0 )
ctx = run_command ( cmd_name )
Logs . info ( ' %r finished successfully ( %s ) ' , cmd_name , ctx . log_timer )
run_command ( ' shutdown ' )
###########################################################################################
def distclean_dir ( dirname ) :
"""
Distclean function called in the particular case when : :
top == out
: param dirname : absolute path of the folder to clean
: type dirname : string
"""
for ( root , dirs , files ) in os . walk ( dirname ) :
for f in files :
if f . endswith ( ( ' .o ' , ' .moc ' , ' .exe ' ) ) :
fname = os . path . join ( root , f )
try :
os . remove ( fname )
except OSError :
Logs . warn ( ' Could not remove %r ' , fname )
for x in ( Context . DBFILE , ' config.log ' ) :
try :
os . remove ( x )
except OSError :
pass
try :
shutil . rmtree ( ' c4che ' )
except OSError :
pass
def distclean ( ctx ) :
2018-01-31 11:48:43 +02:00
''' removes build folders and data '''
2016-03-26 13:32:11 +01:00
2018-01-31 11:48:43 +02:00
def remove_and_log ( k , fun ) :
try :
fun ( k )
except EnvironmentError as e :
if e . errno != errno . ENOENT :
Logs . warn ( ' Could not remove %r ' , k )
2016-03-26 13:32:11 +01:00
2018-01-31 11:48:43 +02:00
# remove waf cache folders on the top-level
if not Options . commands :
for k in os . listdir ( ' . ' ) :
for x in ' .waf-2 waf-2 .waf3-2 waf3-2 ' . split ( ) :
if k . startswith ( x ) :
remove_and_log ( k , shutil . rmtree )
# remove a build folder, if any
cur = ' . '
if ctx . options . no_lock_in_top :
cur = ctx . options . out
try :
lst = os . listdir ( cur )
except OSError :
Logs . warn ( ' Could not read %r ' , cur )
return
if Options . lockfile in lst :
f = os . path . join ( cur , Options . lockfile )
try :
env = ConfigSet . ConfigSet ( f )
except EnvironmentError :
Logs . warn ( ' Could not read %r ' , f )
return
2016-03-26 13:32:11 +01:00
2018-01-31 11:48:43 +02:00
if not env . out_dir or not env . top_dir :
Logs . warn ( ' Invalid lock file %r ' , f )
return
if env . out_dir == env . top_dir :
distclean_dir ( env . out_dir )
else :
remove_and_log ( env . out_dir , shutil . rmtree )
for k in ( env . out_dir , env . top_dir , env . run_dir ) :
p = os . path . join ( k , Options . lockfile )
remove_and_log ( p , os . remove )
2016-03-26 13:32:11 +01:00
class Dist ( Context . Context ) :
''' creates an archive containing the project source code '''
cmd = ' dist '
fun = ' dist '
algo = ' tar.bz2 '
ext_algo = { }
def execute ( self ) :
"""
See : py : func : ` waflib . Context . Context . execute `
"""
self . recurse ( [ os . path . dirname ( Context . g_module . root_path ) ] )
self . archive ( )
def archive ( self ) :
"""
Creates the source archive .
"""
import tarfile
arch_name = self . get_arch_name ( )
try :
self . base_path
except AttributeError :
self . base_path = self . path
node = self . base_path . make_node ( arch_name )
try :
node . delete ( )
except OSError :
pass
files = self . get_files ( )
if self . algo . startswith ( ' tar. ' ) :
tar = tarfile . open ( node . abspath ( ) , ' w: ' + self . algo . replace ( ' tar. ' , ' ' ) )
for x in files :
self . add_tar_file ( x , tar )
tar . close ( )
elif self . algo == ' zip ' :
import zipfile
zip = zipfile . ZipFile ( node . abspath ( ) , ' w ' , compression = zipfile . ZIP_DEFLATED )
for x in files :
archive_name = self . get_base_name ( ) + ' / ' + x . path_from ( self . base_path )
zip . write ( x . abspath ( ) , archive_name , zipfile . ZIP_DEFLATED )
zip . close ( )
else :
self . fatal ( ' Valid algo types are tar.bz2, tar.gz, tar.xz or zip ' )
try :
2018-01-31 11:48:43 +02:00
from hashlib import sha256
2016-03-26 13:32:11 +01:00
except ImportError :
digest = ' '
else :
2018-01-31 11:48:43 +02:00
digest = ' (sha256= %r ) ' % sha256 ( node . read ( flags = ' rb ' ) ) . hexdigest ( )
2016-03-26 13:32:11 +01:00
Logs . info ( ' New archive created: %s %s ' , self . arch_name , digest )
def get_tar_path ( self , node ) :
"""
Return the path to use for a node in the tar archive , the purpose of this
is to let subclases resolve symbolic links or to change file names
: return : absolute path
: rtype : string
"""
return node . abspath ( )
def add_tar_file ( self , x , tar ) :
"""
Adds a file to the tar archive . Symlinks are not verified .
: param x : file path
: param tar : tar file object
"""
p = self . get_tar_path ( x )
tinfo = tar . gettarinfo ( name = p , arcname = self . get_tar_prefix ( ) + ' / ' + x . path_from ( self . base_path ) )
tinfo . uid = 0
tinfo . gid = 0
tinfo . uname = ' root '
tinfo . gname = ' root '
if os . path . isfile ( p ) :
2018-01-31 11:48:43 +02:00
with open ( p , ' rb ' ) as f :
tar . addfile ( tinfo , fileobj = f )
2016-03-26 13:32:11 +01:00
else :
tar . addfile ( tinfo )
def get_tar_prefix ( self ) :
"""
Returns the base path for files added into the archive tar file
: rtype : string
"""
try :
return self . tar_prefix
except AttributeError :
return self . get_base_name ( )
def get_arch_name ( self ) :
"""
Returns the archive file name .
Set the attribute * arch_name * to change the default value : :
def dist ( ctx ) :
ctx . arch_name = ' ctx.tar.bz2 '
: rtype : string
"""
try :
self . arch_name
except AttributeError :
self . arch_name = self . get_base_name ( ) + ' . ' + self . ext_algo . get ( self . algo , self . algo )
return self . arch_name
def get_base_name ( self ) :
"""
Returns the default name of the main directory in the archive , which is set to * appname - version * .
Set the attribute * base_name * to change the default value : :
def dist ( ctx ) :
ctx . base_name = ' files '
: rtype : string
"""
try :
self . base_name
except AttributeError :
appname = getattr ( Context . g_module , Context . APPNAME , ' noname ' )
version = getattr ( Context . g_module , Context . VERSION , ' 1.0 ' )
self . base_name = appname + ' - ' + version
return self . base_name
def get_excl ( self ) :
"""
Returns the patterns to exclude for finding the files in the top - level directory .
Set the attribute * excl * to change the default value : :
def dist ( ctx ) :
ctx . excl = ' build **/*.o **/*.class '
: rtype : string
"""
try :
return self . excl
except AttributeError :
2018-01-31 11:48:43 +02:00
self . excl = Node . exclude_regs + ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w* '
2016-03-26 13:32:11 +01:00
if Context . out_dir :
nd = self . root . find_node ( Context . out_dir )
if nd :
self . excl + = ' ' + nd . path_from ( self . base_path )
return self . excl
def get_files ( self ) :
"""
Files to package are searched automatically by : py : func : ` waflib . Node . Node . ant_glob ` .
Set * files * to prevent this behaviour : :
def dist ( ctx ) :
ctx . files = ctx . path . find_node ( ' wscript ' )
Files are also searched from the directory ' base_path ' , to change it , set : :
def dist ( ctx ) :
ctx . base_path = path
: rtype : list of : py : class : ` waflib . Node . Node `
"""
try :
files = self . files
except AttributeError :
files = self . base_path . ant_glob ( ' **/* ' , excl = self . get_excl ( ) )
return files
def dist ( ctx ) :
''' makes a tarball for redistributing the sources '''
pass
class DistCheck ( Dist ) :
2018-01-31 11:48:43 +02:00
""" creates an archive with dist, then tries to build it """
2016-03-26 13:32:11 +01:00
fun = ' distcheck '
cmd = ' distcheck '
def execute ( self ) :
"""
See : py : func : ` waflib . Context . Context . execute `
"""
self . recurse ( [ os . path . dirname ( Context . g_module . root_path ) ] )
self . archive ( )
self . check ( )
def make_distcheck_cmd ( self , tmpdir ) :
cfg = [ ]
if Options . options . distcheck_args :
cfg = shlex . split ( Options . options . distcheck_args )
else :
cfg = [ x for x in sys . argv if x . startswith ( ' - ' ) ]
cmd = [ sys . executable , sys . argv [ 0 ] , ' configure ' , ' build ' , ' install ' , ' uninstall ' , ' --destdir= ' + tmpdir ] + cfg
return cmd
def check ( self ) :
"""
Creates the archive , uncompresses it and tries to build the project
"""
import tempfile , tarfile
2018-01-31 11:48:43 +02:00
with tarfile . open ( self . get_arch_name ( ) ) as t :
2016-03-26 13:32:11 +01:00
for x in t :
t . extract ( x )
instdir = tempfile . mkdtemp ( ' .inst ' , self . get_base_name ( ) )
cmd = self . make_distcheck_cmd ( instdir )
ret = Utils . subprocess . Popen ( cmd , cwd = self . get_base_name ( ) ) . wait ( )
if ret :
raise Errors . WafError ( ' distcheck failed with code %r ' % ret )
if os . path . exists ( instdir ) :
raise Errors . WafError ( ' distcheck succeeded, but files were left in %s ' % instdir )
shutil . rmtree ( self . get_base_name ( ) )
def distcheck ( ctx ) :
''' checks if the project compiles (tarball from ' dist ' ) '''
pass
def autoconfigure ( execute_method ) :
"""
Decorator that enables context commands to run * configure * as needed .
"""
def execute ( self ) :
"""
Wraps : py : func : ` waflib . Context . Context . execute ` on the context class
"""
if not Configure . autoconfig :
return execute_method ( self )
env = ConfigSet . ConfigSet ( )
do_config = False
try :
env . load ( os . path . join ( Context . top_dir , Options . lockfile ) )
except EnvironmentError :
Logs . warn ( ' Configuring the project ' )
do_config = True
else :
if env . run_dir != Context . run_dir :
do_config = True
else :
h = 0
for f in env . files :
try :
h = Utils . h_list ( ( h , Utils . readf ( f , ' rb ' ) ) )
except EnvironmentError :
do_config = True
break
else :
do_config = h != env . hash
if do_config :
cmd = env . config_cmd or ' configure '
if Configure . autoconfig == ' clobber ' :
tmp = Options . options . __dict__
2018-01-31 11:48:43 +02:00
if env . options :
Options . options . __dict__ = env . options
2016-03-26 13:32:11 +01:00
try :
run_command ( cmd )
finally :
Options . options . __dict__ = tmp
else :
run_command ( cmd )
run_command ( self . cmd )
else :
return execute_method ( self )
return execute
Build . BuildContext . execute = autoconfigure ( Build . BuildContext . execute )
2018-01-31 11:48:43 +02:00