2016-03-26 15:32:11 +03:00
#!/usr/bin/env python
# encoding: utf-8
2018-01-31 12:48:43 +03:00
# Thomas Nagy, 2005-2018 (ita)
2016-03-26 15:32:11 +03:00
"""
Configuration system
A : py : class : ` waflib . Configure . ConfigurationContext ` instance is created when ` ` waf configure ` ` is called , it is used to :
* create data dictionaries ( ConfigSet instances )
* store the list of modules to import
* hold configuration routines such as ` ` find_program ` ` , etc
"""
2018-01-31 12:48:43 +03:00
import os , re , shlex , shutil , sys , time , traceback
2016-03-26 15:32:11 +03:00
from waflib import ConfigSet , Utils , Options , Logs , Context , Build , Errors
WAF_CONFIG_LOG = ' config.log '
""" Name of the configuration log file """
autoconfig = False
""" Execute the configuration automatically """
conf_template = ''' # project %(app)s configured on %(now)s by
# waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
# using %(args)s
#'''
class ConfigurationContext ( Context . Context ) :
''' configures the project '''
cmd = ' configure '
error_handlers = [ ]
"""
Additional functions to handle configuration errors
"""
def __init__ ( self , * * kw ) :
super ( ConfigurationContext , self ) . __init__ ( * * kw )
self . environ = dict ( os . environ )
self . all_envs = { }
self . top_dir = None
self . out_dir = None
self . tools = [ ] # tools loaded in the configuration, and that will be loaded when building
self . hash = 0
self . files = [ ]
self . tool_cache = [ ]
self . setenv ( ' ' )
def setenv ( self , name , env = None ) :
"""
Set a new config set for conf . env . If a config set of that name already exists ,
recall it without modification .
The name is the filename prefix to save to ` ` c4che / NAME_cache . py ` ` , and it
is also used as * variants * by the build commands .
Though related to variants , whatever kind of data may be stored in the config set : :
def configure ( cfg ) :
cfg . env . ONE = 1
cfg . setenv ( ' foo ' )
cfg . env . ONE = 2
def build ( bld ) :
2 == bld . env_of_name ( ' foo ' ) . ONE
: param name : name of the configuration set
: type name : string
: param env : ConfigSet to copy , or an empty ConfigSet is created
: type env : : py : class : ` waflib . ConfigSet . ConfigSet `
"""
if name not in self . all_envs or env :
if not env :
env = ConfigSet . ConfigSet ( )
self . prepare_env ( env )
else :
env = env . derive ( )
self . all_envs [ name ] = env
self . variant = name
def get_env ( self ) :
""" Getter for the env property """
return self . all_envs [ self . variant ]
def set_env ( self , val ) :
""" Setter for the env property """
self . all_envs [ self . variant ] = val
env = property ( get_env , set_env )
def init_dirs ( self ) :
"""
Initialize the project directory and the build directory
"""
top = self . top_dir
if not top :
top = Options . options . top
if not top :
top = getattr ( Context . g_module , Context . TOP , None )
if not top :
top = self . path . abspath ( )
top = os . path . abspath ( top )
self . srcnode = ( os . path . isabs ( top ) and self . root or self . path ) . find_dir ( top )
assert ( self . srcnode )
out = self . out_dir
if not out :
out = Options . options . out
if not out :
out = getattr ( Context . g_module , Context . OUT , None )
if not out :
out = Options . lockfile . replace ( ' .lock-waf_ %s _ ' % sys . platform , ' ' ) . replace ( ' .lock-waf ' , ' ' )
# someone can be messing with symlinks
out = os . path . realpath ( out )
self . bldnode = ( os . path . isabs ( out ) and self . root or self . path ) . make_node ( out )
self . bldnode . mkdir ( )
if not os . path . isdir ( self . bldnode . abspath ( ) ) :
2019-06-03 11:40:55 +03:00
self . fatal ( ' Could not create the build directory %s ' % self . bldnode . abspath ( ) )
2016-03-26 15:32:11 +03:00
def execute ( self ) :
"""
See : py : func : ` waflib . Context . Context . execute `
"""
self . init_dirs ( )
self . cachedir = self . bldnode . make_node ( Build . CACHE_DIR )
self . cachedir . mkdir ( )
path = os . path . join ( self . bldnode . abspath ( ) , WAF_CONFIG_LOG )
self . logger = Logs . make_logger ( path , ' cfg ' )
app = getattr ( Context . g_module , ' APPNAME ' , ' ' )
if app :
ver = getattr ( Context . g_module , ' VERSION ' , ' ' )
if ver :
app = " %s ( %s ) " % ( app , ver )
params = { ' now ' : time . ctime ( ) , ' pyver ' : sys . hexversion , ' systype ' : sys . platform , ' args ' : " " . join ( sys . argv ) , ' wafver ' : Context . WAFVERSION , ' abi ' : Context . ABI , ' app ' : app }
self . to_log ( conf_template % params )
self . msg ( ' Setting top to ' , self . srcnode . abspath ( ) )
self . msg ( ' Setting out to ' , self . bldnode . abspath ( ) )
if id ( self . srcnode ) == id ( self . bldnode ) :
Logs . warn ( ' Setting top == out ' )
elif id ( self . path ) != id ( self . srcnode ) :
if self . srcnode . is_child_of ( self . path ) :
Logs . warn ( ' Are you certain that you do not want to set top= " . " ? ' )
super ( ConfigurationContext , self ) . execute ( )
self . store ( )
Context . top_dir = self . srcnode . abspath ( )
Context . out_dir = self . bldnode . abspath ( )
# this will write a configure lock so that subsequent builds will
# consider the current path as the root directory (see prepare_impl).
# to remove: use 'waf distclean'
env = ConfigSet . ConfigSet ( )
env . argv = sys . argv
env . options = Options . options . __dict__
env . config_cmd = self . cmd
env . run_dir = Context . run_dir
env . top_dir = Context . top_dir
env . out_dir = Context . out_dir
# conf.hash & conf.files hold wscript files paths and hash
# (used only by Configure.autoconfig)
env . hash = self . hash
env . files = self . files
env . environ = dict ( self . environ )
2019-06-03 11:40:55 +03:00
env . launch_dir = Context . launch_dir
2016-03-26 15:32:11 +03:00
if not ( self . env . NO_LOCK_IN_RUN or env . environ . get ( ' NO_LOCK_IN_RUN ' ) or getattr ( Options . options , ' no_lock_in_run ' ) ) :
env . store ( os . path . join ( Context . run_dir , Options . lockfile ) )
if not ( self . env . NO_LOCK_IN_TOP or env . environ . get ( ' NO_LOCK_IN_TOP ' ) or getattr ( Options . options , ' no_lock_in_top ' ) ) :
env . store ( os . path . join ( Context . top_dir , Options . lockfile ) )
if not ( self . env . NO_LOCK_IN_OUT or env . environ . get ( ' NO_LOCK_IN_OUT ' ) or getattr ( Options . options , ' no_lock_in_out ' ) ) :
env . store ( os . path . join ( Context . out_dir , Options . lockfile ) )
def prepare_env ( self , env ) :
"""
Insert * PREFIX * , * BINDIR * and * LIBDIR * values into ` ` env ` `
: type env : : py : class : ` waflib . ConfigSet . ConfigSet `
: param env : a ConfigSet , usually ` ` conf . env ` `
"""
if not env . PREFIX :
if Options . options . prefix or Utils . is_win32 :
2018-01-31 12:48:43 +03:00
env . PREFIX = Options . options . prefix
2016-03-26 15:32:11 +03:00
else :
2018-01-31 12:48:43 +03:00
env . PREFIX = ' / '
2016-03-26 15:32:11 +03:00
if not env . BINDIR :
if Options . options . bindir :
2018-01-31 12:48:43 +03:00
env . BINDIR = Options . options . bindir
2016-03-26 15:32:11 +03:00
else :
env . BINDIR = Utils . subst_vars ( ' $ {PREFIX} /bin ' , env )
if not env . LIBDIR :
if Options . options . libdir :
2018-01-31 12:48:43 +03:00
env . LIBDIR = Options . options . libdir
2016-03-26 15:32:11 +03:00
else :
env . LIBDIR = Utils . subst_vars ( ' $ {PREFIX} /lib %s ' % Utils . lib64 ( ) , env )
def store ( self ) :
""" Save the config results into the cache file """
n = self . cachedir . make_node ( ' build.config.py ' )
n . write ( ' version = 0x %x \n tools = %r \n ' % ( Context . HEXVERSION , self . tools ) )
if not self . all_envs :
self . fatal ( ' nothing to store in the configuration context! ' )
for key in self . all_envs :
tmpenv = self . all_envs [ key ]
tmpenv . store ( os . path . join ( self . cachedir . abspath ( ) , key + Build . CACHE_SUFFIX ) )
2018-01-31 12:48:43 +03:00
def load ( self , tool_list , tooldir = None , funs = None , with_sys_path = True , cache = False ) :
2016-03-26 15:32:11 +03:00
"""
Load Waf tools , which will be imported whenever a build is started .
2018-01-31 12:48:43 +03:00
: param tool_list : waf tools to import
: type tool_list : list of string
2016-03-26 15:32:11 +03:00
: param tooldir : paths for the imports
: type tooldir : list of string
: param funs : functions to execute from the waf tools
: type funs : list of string
: param cache : whether to prevent the tool from running twice
: type cache : bool
"""
2018-01-31 12:48:43 +03:00
tools = Utils . to_list ( tool_list )
if tooldir :
tooldir = Utils . to_list ( tooldir )
2016-03-26 15:32:11 +03:00
for tool in tools :
# avoid loading the same tool more than once with the same functions
# used by composite projects
if cache :
mag = ( tool , id ( self . env ) , tooldir , funs )
if mag in self . tool_cache :
self . to_log ( ' (tool %s is already loaded, skipping) ' % tool )
continue
self . tool_cache . append ( mag )
module = None
try :
module = Context . load_tool ( tool , tooldir , ctx = self , with_sys_path = with_sys_path )
2018-01-31 12:48:43 +03:00
except ImportError as e :
self . fatal ( ' Could not load the Waf tool %r from %r \n %s ' % ( tool , getattr ( e , ' waf_sys_path ' , sys . path ) , e ) )
except Exception as e :
2016-03-26 15:32:11 +03:00
self . to_log ( ' imp %r ( %r & %r ) ' % ( tool , tooldir , funs ) )
2018-01-31 12:48:43 +03:00
self . to_log ( traceback . format_exc ( ) )
2016-03-26 15:32:11 +03:00
raise
if funs is not None :
self . eval_rules ( funs )
else :
func = getattr ( module , ' configure ' , None )
if func :
2018-01-31 12:48:43 +03:00
if type ( func ) is type ( Utils . readf ) :
func ( self )
else :
self . eval_rules ( func )
2016-03-26 15:32:11 +03:00
self . tools . append ( { ' tool ' : tool , ' tooldir ' : tooldir , ' funs ' : funs } )
def post_recurse ( self , node ) :
"""
Records the path and a hash of the scripts visited , see : py : meth : ` waflib . Context . Context . post_recurse `
: param node : script
: type node : : py : class : ` waflib . Node . Node `
"""
super ( ConfigurationContext , self ) . post_recurse ( node )
self . hash = Utils . h_list ( ( self . hash , node . read ( ' rb ' ) ) )
self . files . append ( node . abspath ( ) )
def eval_rules ( self , rules ) :
"""
2019-06-03 11:40:55 +03:00
Execute configuration tests provided as list of functions to run
2016-03-26 15:32:11 +03:00
: param rules : list of configuration method names
: type rules : list of string
"""
self . rules = Utils . to_list ( rules )
for x in self . rules :
f = getattr ( self , x )
if not f :
self . fatal ( ' No such configuration function %r ' % x )
f ( )
def conf ( f ) :
"""
Decorator : attach new configuration functions to : py : class : ` waflib . Build . BuildContext ` and
: py : class : ` waflib . Configure . ConfigurationContext ` . The methods bound will accept a parameter
named ' mandatory ' to disable the configuration errors : :
def configure ( conf ) :
conf . find_program ( ' abc ' , mandatory = False )
: param f : method to bind
: type f : function
"""
def fun ( * k , * * kw ) :
2018-06-15 13:29:45 +03:00
mandatory = kw . pop ( ' mandatory ' , True )
2016-03-26 15:32:11 +03:00
try :
return f ( * k , * * kw )
except Errors . ConfigurationError :
if mandatory :
raise
fun . __name__ = f . __name__
setattr ( ConfigurationContext , f . __name__ , fun )
setattr ( Build . BuildContext , f . __name__ , fun )
return f
@conf
def add_os_flags ( self , var , dest = None , dup = False ) :
"""
Import operating system environment values into ` ` conf . env ` ` dict : :
def configure ( conf ) :
conf . add_os_flags ( ' CFLAGS ' )
: param var : variable to use
: type var : string
: param dest : destination variable , by default the same as var
: type dest : string
: param dup : add the same set of flags again
: type dup : bool
"""
try :
flags = shlex . split ( self . environ [ var ] )
except KeyError :
return
if dup or ' ' . join ( flags ) not in ' ' . join ( Utils . to_list ( self . env [ dest or var ] ) ) :
self . env . append_value ( dest or var , flags )
@conf
def cmd_to_list ( self , cmd ) :
"""
Detect if a command is written in pseudo shell like ` ` ccache g + + ` ` and return a list .
: param cmd : command
: type cmd : a string or a list of string
"""
if isinstance ( cmd , str ) :
if os . path . isfile ( cmd ) :
# do not take any risk
return [ cmd ]
if os . sep == ' / ' :
return shlex . split ( cmd )
else :
try :
return shlex . split ( cmd , posix = False )
except TypeError :
# Python 2.5 on windows?
return shlex . split ( cmd )
return cmd
@conf
2018-01-31 12:48:43 +03:00
def check_waf_version ( self , mini = ' 1.9.99 ' , maxi = ' 2.1.0 ' , * * kw ) :
2016-03-26 15:32:11 +03:00
"""
Raise a Configuration error if the Waf version does not strictly match the given bounds : :
2018-01-31 12:48:43 +03:00
conf . check_waf_version ( mini = ' 1.9.99 ' , maxi = ' 2.1.0 ' )
2016-03-26 15:32:11 +03:00
: type mini : number , tuple or string
: param mini : Minimum required version
: type maxi : number , tuple or string
: param maxi : Maximum allowed version
"""
self . start_msg ( ' Checking for waf version in %s - %s ' % ( str ( mini ) , str ( maxi ) ) , * * kw )
ver = Context . HEXVERSION
if Utils . num2ver ( mini ) > ver :
self . fatal ( ' waf version should be at least %r ( %r found) ' % ( Utils . num2ver ( mini ) , ver ) )
if Utils . num2ver ( maxi ) < ver :
self . fatal ( ' waf version should be at most %r ( %r found) ' % ( Utils . num2ver ( maxi ) , ver ) )
self . end_msg ( ' ok ' , * * kw )
@conf
def find_file ( self , filename , path_list = [ ] ) :
"""
Find a file in a list of paths
: param filename : name of the file to search for
: param path_list : list of directories to search
2018-01-31 12:48:43 +03:00
: return : the first matching filename ; else a configuration exception is raised
2016-03-26 15:32:11 +03:00
"""
for n in Utils . to_list ( filename ) :
for d in Utils . to_list ( path_list ) :
p = os . path . expanduser ( os . path . join ( d , n ) )
if os . path . exists ( p ) :
return p
self . fatal ( ' Could not find %r ' % filename )
@conf
def find_program ( self , filename , * * kw ) :
"""
Search for a program on the operating system
When var is used , you may set os . environ [ var ] to help find a specific program version , for example : :
$ CC = ' ccache gcc ' waf configure
: param path_list : paths to use for searching
: type param_list : list of string
: param var : store the result to conf . env [ var ] where var defaults to filename . upper ( ) if not provided ; the result is stored as a list of strings
: type var : string
: param value : obtain the program from the value passed exclusively
: type value : list or string ( list is preferred )
2018-06-15 13:29:45 +03:00
: param exts : list of extensions for the binary ( do not add an extension for portability )
: type exts : list of string
2016-03-26 15:32:11 +03:00
: param msg : name to display in the log , by default filename is used
: type msg : string
: param interpreter : interpreter for the program
: type interpreter : ConfigSet variable key
2018-01-31 12:48:43 +03:00
: raises : : py : class : ` waflib . Errors . ConfigurationError `
2016-03-26 15:32:11 +03:00
"""
exts = kw . get ( ' exts ' , Utils . is_win32 and ' .exe,.com,.bat,.cmd ' or ' ,.sh,.pl,.py ' )
environ = kw . get ( ' environ ' , getattr ( self , ' environ ' , os . environ ) )
ret = ' '
filename = Utils . to_list ( filename )
msg = kw . get ( ' msg ' , ' , ' . join ( filename ) )
var = kw . get ( ' var ' , ' ' )
if not var :
var = re . sub ( r ' [-.] ' , ' _ ' , filename [ 0 ] . upper ( ) )
path_list = kw . get ( ' path_list ' , ' ' )
if path_list :
path_list = Utils . to_list ( path_list )
else :
path_list = environ . get ( ' PATH ' , ' ' ) . split ( os . pathsep )
if kw . get ( ' value ' ) :
# user-provided in command-line options and passed to find_program
ret = self . cmd_to_list ( kw [ ' value ' ] )
elif environ . get ( var ) :
# user-provided in the os environment
ret = self . cmd_to_list ( environ [ var ] )
elif self . env [ var ] :
# a default option in the wscript file
ret = self . cmd_to_list ( self . env [ var ] )
else :
if not ret :
ret = self . find_binary ( filename , exts . split ( ' , ' ) , path_list )
if not ret and Utils . winreg :
ret = Utils . get_registry_app_path ( Utils . winreg . HKEY_CURRENT_USER , filename )
if not ret and Utils . winreg :
ret = Utils . get_registry_app_path ( Utils . winreg . HKEY_LOCAL_MACHINE , filename )
ret = self . cmd_to_list ( ret )
if ret :
if len ( ret ) == 1 :
retmsg = ret [ 0 ]
else :
retmsg = ret
else :
retmsg = False
self . msg ( ' Checking for program %r ' % msg , retmsg , * * kw )
if not kw . get ( ' quiet ' ) :
self . to_log ( ' find program= %r paths= %r var= %r -> %r ' % ( filename , path_list , var , ret ) )
if not ret :
self . fatal ( kw . get ( ' errmsg ' , ' ' ) or ' Could not find the program %r ' % filename )
interpreter = kw . get ( ' interpreter ' )
if interpreter is None :
if not Utils . check_exe ( ret [ 0 ] , env = environ ) :
self . fatal ( ' Program %r is not executable ' % ret )
self . env [ var ] = ret
else :
self . env [ var ] = self . env [ interpreter ] + ret
return ret
@conf
def find_binary ( self , filenames , exts , paths ) :
for f in filenames :
for ext in exts :
exe_name = f + ext
if os . path . isabs ( exe_name ) :
if os . path . isfile ( exe_name ) :
return exe_name
else :
for path in paths :
x = os . path . expanduser ( os . path . join ( path , exe_name ) )
if os . path . isfile ( x ) :
return x
return None
@conf
def run_build ( self , * k , * * kw ) :
"""
Create a temporary build context to execute a build . A reference to that build
context is kept on self . test_bld for debugging purposes , and you should not rely
on it too much ( read the note on the cache below ) .
The parameters given in the arguments to this function are passed as arguments for
a single task generator created in the build . Only three parameters are obligatory :
: param features : features to pass to a task generator created in the build
: type features : list of string
: param compile_filename : file to create for the compilation ( default : * test . c * )
: type compile_filename : string
: param code : code to write in the filename to compile
: type code : string
Though this function returns * 0 * by default , the build may set an attribute named * retval * on the
build context object to return a particular value . See : py : func : ` waflib . Tools . c_config . test_exec_fun ` for example .
2019-10-07 00:36:42 +03:00
This function also features a cache which can be enabled by the following option : :
2016-03-26 15:32:11 +03:00
def options ( opt ) :
opt . add_option ( ' --confcache ' , dest = ' confcache ' , default = 0 ,
action = ' count ' , help = ' Use a configuration cache ' )
And execute the configuration with the following command - line : :
$ waf configure - - confcache
"""
2019-10-07 00:36:42 +03:00
buf = [ ]
for key in sorted ( kw . keys ( ) ) :
v = kw [ key ]
if hasattr ( v , ' __call__ ' ) :
buf . append ( Utils . h_fun ( v ) )
else :
buf . append ( str ( v ) )
h = Utils . h_list ( buf )
2016-03-26 15:32:11 +03:00
dir = self . bldnode . abspath ( ) + os . sep + ( not Utils . is_win32 and ' . ' or ' ' ) + ' conf_check_ ' + Utils . to_hex ( h )
2019-10-07 00:36:42 +03:00
cachemode = kw . get ( ' confcache ' , getattr ( Options . options , ' confcache ' , None ) )
if not cachemode and os . path . exists ( dir ) :
shutil . rmtree ( dir )
2016-03-26 15:32:11 +03:00
try :
os . makedirs ( dir )
except OSError :
pass
try :
os . stat ( dir )
except OSError :
self . fatal ( ' cannot use the configuration test folder %r ' % dir )
if cachemode == 1 :
try :
proj = ConfigSet . ConfigSet ( os . path . join ( dir , ' cache_run_build ' ) )
except EnvironmentError :
pass
else :
ret = proj [ ' cache_run_build ' ]
if isinstance ( ret , str ) and ret . startswith ( ' Test does not build ' ) :
self . fatal ( ret )
return ret
bdir = os . path . join ( dir , ' testbuild ' )
if not os . path . exists ( bdir ) :
os . makedirs ( bdir )
cls_name = kw . get ( ' run_build_cls ' ) or getattr ( self , ' run_build_cls ' , ' build ' )
self . test_bld = bld = Context . create_context ( cls_name , top_dir = dir , out_dir = bdir )
bld . init_dirs ( )
bld . progress_bar = 0
bld . targets = ' * '
bld . logger = self . logger
bld . all_envs . update ( self . all_envs ) # not really necessary
bld . env = kw [ ' env ' ]
bld . kw = kw
bld . conf = self
kw [ ' build_fun ' ] ( bld )
ret = - 1
try :
try :
bld . compile ( )
except Errors . WafError :
2018-01-31 12:48:43 +03:00
ret = ' Test does not build: %s ' % traceback . format_exc ( )
2016-03-26 15:32:11 +03:00
self . fatal ( ret )
else :
ret = getattr ( bld , ' retval ' , 0 )
finally :
2019-10-07 00:36:42 +03:00
if cachemode :
2016-03-26 15:32:11 +03:00
# cache the results each time
proj = ConfigSet . ConfigSet ( )
proj [ ' cache_run_build ' ] = ret
proj . store ( os . path . join ( dir , ' cache_run_build ' ) )
else :
shutil . rmtree ( dir )
return ret
@conf
def ret_msg ( self , msg , args ) :
if isinstance ( msg , str ) :
return msg
return msg ( args )
@conf
def test ( self , * k , * * kw ) :
if not ' env ' in kw :
kw [ ' env ' ] = self . env . derive ( )
# validate_c for example
if kw . get ( ' validate ' ) :
kw [ ' validate ' ] ( kw )
self . start_msg ( kw [ ' msg ' ] , * * kw )
ret = None
try :
ret = self . run_build ( * k , * * kw )
except self . errors . ConfigurationError :
self . end_msg ( kw [ ' errmsg ' ] , ' YELLOW ' , * * kw )
if Logs . verbose > 1 :
raise
else :
self . fatal ( ' The configuration failed ' )
else :
kw [ ' success ' ] = ret
if kw . get ( ' post_check ' ) :
ret = kw [ ' post_check ' ] ( kw )
if ret :
self . end_msg ( kw [ ' errmsg ' ] , ' YELLOW ' , * * kw )
self . fatal ( ' The configuration failed %r ' % ret )
else :
self . end_msg ( self . ret_msg ( kw [ ' okmsg ' ] , kw ) , * * kw )
return ret
2018-01-31 12:48:43 +03:00