Configure.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2018 (ita)
  4. """
  5. Configuration system
  6. A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``waf configure`` is called, it is used to:
  7. * create data dictionaries (ConfigSet instances)
  8. * store the list of modules to import
  9. * hold configuration routines such as ``find_program``, etc
  10. """
  11. import os, re, shlex, shutil, sys, time, traceback
  12. from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors
  13. WAF_CONFIG_LOG = 'config.log'
  14. """Name of the configuration log file"""
  15. autoconfig = False
  16. """Execute the configuration automatically"""
  17. conf_template = '''# project %(app)s configured on %(now)s by
  18. # waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
  19. # using %(args)s
  20. #'''
  21. class ConfigurationContext(Context.Context):
  22. '''configures the project'''
  23. cmd = 'configure'
  24. error_handlers = []
  25. """
  26. Additional functions to handle configuration errors
  27. """
  28. def __init__(self, **kw):
  29. super(ConfigurationContext, self).__init__(**kw)
  30. self.environ = dict(os.environ)
  31. self.all_envs = {}
  32. self.top_dir = None
  33. self.out_dir = None
  34. self.tools = [] # tools loaded in the configuration, and that will be loaded when building
  35. self.hash = 0
  36. self.files = []
  37. self.tool_cache = []
  38. self.setenv('')
  39. def setenv(self, name, env=None):
  40. """
  41. Set a new config set for conf.env. If a config set of that name already exists,
  42. recall it without modification.
  43. The name is the filename prefix to save to ``c4che/NAME_cache.py``, and it
  44. is also used as *variants* by the build commands.
  45. Though related to variants, whatever kind of data may be stored in the config set::
  46. def configure(cfg):
  47. cfg.env.ONE = 1
  48. cfg.setenv('foo')
  49. cfg.env.ONE = 2
  50. def build(bld):
  51. 2 == bld.env_of_name('foo').ONE
  52. :param name: name of the configuration set
  53. :type name: string
  54. :param env: ConfigSet to copy, or an empty ConfigSet is created
  55. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  56. """
  57. if name not in self.all_envs or env:
  58. if not env:
  59. env = ConfigSet.ConfigSet()
  60. self.prepare_env(env)
  61. else:
  62. env = env.derive()
  63. self.all_envs[name] = env
  64. self.variant = name
  65. def get_env(self):
  66. """Getter for the env property"""
  67. return self.all_envs[self.variant]
  68. def set_env(self, val):
  69. """Setter for the env property"""
  70. self.all_envs[self.variant] = val
  71. env = property(get_env, set_env)
  72. def init_dirs(self):
  73. """
  74. Initialize the project directory and the build directory
  75. """
  76. top = self.top_dir
  77. if not top:
  78. top = Options.options.top
  79. if not top:
  80. top = getattr(Context.g_module, Context.TOP, None)
  81. if not top:
  82. top = self.path.abspath()
  83. top = os.path.abspath(top)
  84. self.srcnode = (os.path.isabs(top) and self.root or self.path).find_dir(top)
  85. assert(self.srcnode)
  86. out = self.out_dir
  87. if not out:
  88. out = Options.options.out
  89. if not out:
  90. out = getattr(Context.g_module, Context.OUT, None)
  91. if not out:
  92. out = Options.lockfile.replace('.lock-waf_%s_' % sys.platform, '').replace('.lock-waf', '')
  93. # someone can be messing with symlinks
  94. out = os.path.realpath(out)
  95. self.bldnode = (os.path.isabs(out) and self.root or self.path).make_node(out)
  96. self.bldnode.mkdir()
  97. if not os.path.isdir(self.bldnode.abspath()):
  98. conf.fatal('Could not create the build directory %s' % self.bldnode.abspath())
  99. def execute(self):
  100. """
  101. See :py:func:`waflib.Context.Context.execute`
  102. """
  103. self.init_dirs()
  104. self.cachedir = self.bldnode.make_node(Build.CACHE_DIR)
  105. self.cachedir.mkdir()
  106. path = os.path.join(self.bldnode.abspath(), WAF_CONFIG_LOG)
  107. self.logger = Logs.make_logger(path, 'cfg')
  108. app = getattr(Context.g_module, 'APPNAME', '')
  109. if app:
  110. ver = getattr(Context.g_module, 'VERSION', '')
  111. if ver:
  112. app = "%s (%s)" % (app, ver)
  113. params = {'now': time.ctime(), 'pyver': sys.hexversion, 'systype': sys.platform, 'args': " ".join(sys.argv), 'wafver': Context.WAFVERSION, 'abi': Context.ABI, 'app': app}
  114. self.to_log(conf_template % params)
  115. self.msg('Setting top to', self.srcnode.abspath())
  116. self.msg('Setting out to', self.bldnode.abspath())
  117. if id(self.srcnode) == id(self.bldnode):
  118. Logs.warn('Setting top == out')
  119. elif id(self.path) != id(self.srcnode):
  120. if self.srcnode.is_child_of(self.path):
  121. Logs.warn('Are you certain that you do not want to set top="." ?')
  122. super(ConfigurationContext, self).execute()
  123. self.store()
  124. Context.top_dir = self.srcnode.abspath()
  125. Context.out_dir = self.bldnode.abspath()
  126. # this will write a configure lock so that subsequent builds will
  127. # consider the current path as the root directory (see prepare_impl).
  128. # to remove: use 'waf distclean'
  129. env = ConfigSet.ConfigSet()
  130. env.argv = sys.argv
  131. env.options = Options.options.__dict__
  132. env.config_cmd = self.cmd
  133. env.run_dir = Context.run_dir
  134. env.top_dir = Context.top_dir
  135. env.out_dir = Context.out_dir
  136. # conf.hash & conf.files hold wscript files paths and hash
  137. # (used only by Configure.autoconfig)
  138. env.hash = self.hash
  139. env.files = self.files
  140. env.environ = dict(self.environ)
  141. if not (self.env.NO_LOCK_IN_RUN or env.environ.get('NO_LOCK_IN_RUN') or getattr(Options.options, 'no_lock_in_run')):
  142. env.store(os.path.join(Context.run_dir, Options.lockfile))
  143. if not (self.env.NO_LOCK_IN_TOP or env.environ.get('NO_LOCK_IN_TOP') or getattr(Options.options, 'no_lock_in_top')):
  144. env.store(os.path.join(Context.top_dir, Options.lockfile))
  145. if not (self.env.NO_LOCK_IN_OUT or env.environ.get('NO_LOCK_IN_OUT') or getattr(Options.options, 'no_lock_in_out')):
  146. env.store(os.path.join(Context.out_dir, Options.lockfile))
  147. def prepare_env(self, env):
  148. """
  149. Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``
  150. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  151. :param env: a ConfigSet, usually ``conf.env``
  152. """
  153. if not env.PREFIX:
  154. if Options.options.prefix or Utils.is_win32:
  155. env.PREFIX = Options.options.prefix
  156. else:
  157. env.PREFIX = '/'
  158. if not env.BINDIR:
  159. if Options.options.bindir:
  160. env.BINDIR = Options.options.bindir
  161. else:
  162. env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env)
  163. if not env.LIBDIR:
  164. if Options.options.libdir:
  165. env.LIBDIR = Options.options.libdir
  166. else:
  167. env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env)
  168. def store(self):
  169. """Save the config results into the cache file"""
  170. n = self.cachedir.make_node('build.config.py')
  171. n.write('version = 0x%x\ntools = %r\n' % (Context.HEXVERSION, self.tools))
  172. if not self.all_envs:
  173. self.fatal('nothing to store in the configuration context!')
  174. for key in self.all_envs:
  175. tmpenv = self.all_envs[key]
  176. tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX))
  177. def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False):
  178. """
  179. Load Waf tools, which will be imported whenever a build is started.
  180. :param tool_list: waf tools to import
  181. :type tool_list: list of string
  182. :param tooldir: paths for the imports
  183. :type tooldir: list of string
  184. :param funs: functions to execute from the waf tools
  185. :type funs: list of string
  186. :param cache: whether to prevent the tool from running twice
  187. :type cache: bool
  188. """
  189. tools = Utils.to_list(tool_list)
  190. if tooldir:
  191. tooldir = Utils.to_list(tooldir)
  192. for tool in tools:
  193. # avoid loading the same tool more than once with the same functions
  194. # used by composite projects
  195. if cache:
  196. mag = (tool, id(self.env), tooldir, funs)
  197. if mag in self.tool_cache:
  198. self.to_log('(tool %s is already loaded, skipping)' % tool)
  199. continue
  200. self.tool_cache.append(mag)
  201. module = None
  202. try:
  203. module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path)
  204. except ImportError as e:
  205. self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e))
  206. except Exception as e:
  207. self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs))
  208. self.to_log(traceback.format_exc())
  209. raise
  210. if funs is not None:
  211. self.eval_rules(funs)
  212. else:
  213. func = getattr(module, 'configure', None)
  214. if func:
  215. if type(func) is type(Utils.readf):
  216. func(self)
  217. else:
  218. self.eval_rules(func)
  219. self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})
  220. def post_recurse(self, node):
  221. """
  222. Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`
  223. :param node: script
  224. :type node: :py:class:`waflib.Node.Node`
  225. """
  226. super(ConfigurationContext, self).post_recurse(node)
  227. self.hash = Utils.h_list((self.hash, node.read('rb')))
  228. self.files.append(node.abspath())
  229. def eval_rules(self, rules):
  230. """
  231. Execute configuration tests provided as list of funcitons to run
  232. :param rules: list of configuration method names
  233. :type rules: list of string
  234. """
  235. self.rules = Utils.to_list(rules)
  236. for x in self.rules:
  237. f = getattr(self, x)
  238. if not f:
  239. self.fatal('No such configuration function %r' % x)
  240. f()
  241. def conf(f):
  242. """
  243. Decorator: attach new configuration functions to :py:class:`waflib.Build.BuildContext` and
  244. :py:class:`waflib.Configure.ConfigurationContext`. The methods bound will accept a parameter
  245. named 'mandatory' to disable the configuration errors::
  246. def configure(conf):
  247. conf.find_program('abc', mandatory=False)
  248. :param f: method to bind
  249. :type f: function
  250. """
  251. def fun(*k, **kw):
  252. mandatory = kw.pop('mandatory', True)
  253. try:
  254. return f(*k, **kw)
  255. except Errors.ConfigurationError:
  256. if mandatory:
  257. raise
  258. fun.__name__ = f.__name__
  259. setattr(ConfigurationContext, f.__name__, fun)
  260. setattr(Build.BuildContext, f.__name__, fun)
  261. return f
  262. @conf
  263. def add_os_flags(self, var, dest=None, dup=False):
  264. """
  265. Import operating system environment values into ``conf.env`` dict::
  266. def configure(conf):
  267. conf.add_os_flags('CFLAGS')
  268. :param var: variable to use
  269. :type var: string
  270. :param dest: destination variable, by default the same as var
  271. :type dest: string
  272. :param dup: add the same set of flags again
  273. :type dup: bool
  274. """
  275. try:
  276. flags = shlex.split(self.environ[var])
  277. except KeyError:
  278. return
  279. if dup or ''.join(flags) not in ''.join(Utils.to_list(self.env[dest or var])):
  280. self.env.append_value(dest or var, flags)
  281. @conf
  282. def cmd_to_list(self, cmd):
  283. """
  284. Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.
  285. :param cmd: command
  286. :type cmd: a string or a list of string
  287. """
  288. if isinstance(cmd, str):
  289. if os.path.isfile(cmd):
  290. # do not take any risk
  291. return [cmd]
  292. if os.sep == '/':
  293. return shlex.split(cmd)
  294. else:
  295. try:
  296. return shlex.split(cmd, posix=False)
  297. except TypeError:
  298. # Python 2.5 on windows?
  299. return shlex.split(cmd)
  300. return cmd
  301. @conf
  302. def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw):
  303. """
  304. Raise a Configuration error if the Waf version does not strictly match the given bounds::
  305. conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
  306. :type mini: number, tuple or string
  307. :param mini: Minimum required version
  308. :type maxi: number, tuple or string
  309. :param maxi: Maximum allowed version
  310. """
  311. self.start_msg('Checking for waf version in %s-%s' % (str(mini), str(maxi)), **kw)
  312. ver = Context.HEXVERSION
  313. if Utils.num2ver(mini) > ver:
  314. self.fatal('waf version should be at least %r (%r found)' % (Utils.num2ver(mini), ver))
  315. if Utils.num2ver(maxi) < ver:
  316. self.fatal('waf version should be at most %r (%r found)' % (Utils.num2ver(maxi), ver))
  317. self.end_msg('ok', **kw)
  318. @conf
  319. def find_file(self, filename, path_list=[]):
  320. """
  321. Find a file in a list of paths
  322. :param filename: name of the file to search for
  323. :param path_list: list of directories to search
  324. :return: the first matching filename; else a configuration exception is raised
  325. """
  326. for n in Utils.to_list(filename):
  327. for d in Utils.to_list(path_list):
  328. p = os.path.expanduser(os.path.join(d, n))
  329. if os.path.exists(p):
  330. return p
  331. self.fatal('Could not find %r' % filename)
  332. @conf
  333. def find_program(self, filename, **kw):
  334. """
  335. Search for a program on the operating system
  336. When var is used, you may set os.environ[var] to help find a specific program version, for example::
  337. $ CC='ccache gcc' waf configure
  338. :param path_list: paths to use for searching
  339. :type param_list: list of string
  340. :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
  341. :type var: string
  342. :param value: obtain the program from the value passed exclusively
  343. :type value: list or string (list is preferred)
  344. :param exts: list of extensions for the binary (do not add an extension for portability)
  345. :type exts: list of string
  346. :param msg: name to display in the log, by default filename is used
  347. :type msg: string
  348. :param interpreter: interpreter for the program
  349. :type interpreter: ConfigSet variable key
  350. :raises: :py:class:`waflib.Errors.ConfigurationError`
  351. """
  352. exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
  353. environ = kw.get('environ', getattr(self, 'environ', os.environ))
  354. ret = ''
  355. filename = Utils.to_list(filename)
  356. msg = kw.get('msg', ', '.join(filename))
  357. var = kw.get('var', '')
  358. if not var:
  359. var = re.sub(r'[-.]', '_', filename[0].upper())
  360. path_list = kw.get('path_list', '')
  361. if path_list:
  362. path_list = Utils.to_list(path_list)
  363. else:
  364. path_list = environ.get('PATH', '').split(os.pathsep)
  365. if kw.get('value'):
  366. # user-provided in command-line options and passed to find_program
  367. ret = self.cmd_to_list(kw['value'])
  368. elif environ.get(var):
  369. # user-provided in the os environment
  370. ret = self.cmd_to_list(environ[var])
  371. elif self.env[var]:
  372. # a default option in the wscript file
  373. ret = self.cmd_to_list(self.env[var])
  374. else:
  375. if not ret:
  376. ret = self.find_binary(filename, exts.split(','), path_list)
  377. if not ret and Utils.winreg:
  378. ret = Utils.get_registry_app_path(Utils.winreg.HKEY_CURRENT_USER, filename)
  379. if not ret and Utils.winreg:
  380. ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename)
  381. ret = self.cmd_to_list(ret)
  382. if ret:
  383. if len(ret) == 1:
  384. retmsg = ret[0]
  385. else:
  386. retmsg = ret
  387. else:
  388. retmsg = False
  389. self.msg('Checking for program %r' % msg, retmsg, **kw)
  390. if not kw.get('quiet'):
  391. self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret))
  392. if not ret:
  393. self.fatal(kw.get('errmsg', '') or 'Could not find the program %r' % filename)
  394. interpreter = kw.get('interpreter')
  395. if interpreter is None:
  396. if not Utils.check_exe(ret[0], env=environ):
  397. self.fatal('Program %r is not executable' % ret)
  398. self.env[var] = ret
  399. else:
  400. self.env[var] = self.env[interpreter] + ret
  401. return ret
  402. @conf
  403. def find_binary(self, filenames, exts, paths):
  404. for f in filenames:
  405. for ext in exts:
  406. exe_name = f + ext
  407. if os.path.isabs(exe_name):
  408. if os.path.isfile(exe_name):
  409. return exe_name
  410. else:
  411. for path in paths:
  412. x = os.path.expanduser(os.path.join(path, exe_name))
  413. if os.path.isfile(x):
  414. return x
  415. return None
  416. @conf
  417. def run_build(self, *k, **kw):
  418. """
  419. Create a temporary build context to execute a build. A reference to that build
  420. context is kept on self.test_bld for debugging purposes, and you should not rely
  421. on it too much (read the note on the cache below).
  422. The parameters given in the arguments to this function are passed as arguments for
  423. a single task generator created in the build. Only three parameters are obligatory:
  424. :param features: features to pass to a task generator created in the build
  425. :type features: list of string
  426. :param compile_filename: file to create for the compilation (default: *test.c*)
  427. :type compile_filename: string
  428. :param code: code to write in the filename to compile
  429. :type code: string
  430. Though this function returns *0* by default, the build may set an attribute named *retval* on the
  431. build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example.
  432. This function also provides a limited cache. To use it, provide the following option::
  433. def options(opt):
  434. opt.add_option('--confcache', dest='confcache', default=0,
  435. action='count', help='Use a configuration cache')
  436. And execute the configuration with the following command-line::
  437. $ waf configure --confcache
  438. """
  439. lst = [str(v) for (p, v) in kw.items() if p != 'env']
  440. h = Utils.h_list(lst)
  441. dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
  442. try:
  443. os.makedirs(dir)
  444. except OSError:
  445. pass
  446. try:
  447. os.stat(dir)
  448. except OSError:
  449. self.fatal('cannot use the configuration test folder %r' % dir)
  450. cachemode = getattr(Options.options, 'confcache', None)
  451. if cachemode == 1:
  452. try:
  453. proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build'))
  454. except EnvironmentError:
  455. pass
  456. else:
  457. ret = proj['cache_run_build']
  458. if isinstance(ret, str) and ret.startswith('Test does not build'):
  459. self.fatal(ret)
  460. return ret
  461. bdir = os.path.join(dir, 'testbuild')
  462. if not os.path.exists(bdir):
  463. os.makedirs(bdir)
  464. cls_name = kw.get('run_build_cls') or getattr(self, 'run_build_cls', 'build')
  465. self.test_bld = bld = Context.create_context(cls_name, top_dir=dir, out_dir=bdir)
  466. bld.init_dirs()
  467. bld.progress_bar = 0
  468. bld.targets = '*'
  469. bld.logger = self.logger
  470. bld.all_envs.update(self.all_envs) # not really necessary
  471. bld.env = kw['env']
  472. bld.kw = kw
  473. bld.conf = self
  474. kw['build_fun'](bld)
  475. ret = -1
  476. try:
  477. try:
  478. bld.compile()
  479. except Errors.WafError:
  480. ret = 'Test does not build: %s' % traceback.format_exc()
  481. self.fatal(ret)
  482. else:
  483. ret = getattr(bld, 'retval', 0)
  484. finally:
  485. if cachemode == 1:
  486. # cache the results each time
  487. proj = ConfigSet.ConfigSet()
  488. proj['cache_run_build'] = ret
  489. proj.store(os.path.join(dir, 'cache_run_build'))
  490. else:
  491. shutil.rmtree(dir)
  492. return ret
  493. @conf
  494. def ret_msg(self, msg, args):
  495. if isinstance(msg, str):
  496. return msg
  497. return msg(args)
  498. @conf
  499. def test(self, *k, **kw):
  500. if not 'env' in kw:
  501. kw['env'] = self.env.derive()
  502. # validate_c for example
  503. if kw.get('validate'):
  504. kw['validate'](kw)
  505. self.start_msg(kw['msg'], **kw)
  506. ret = None
  507. try:
  508. ret = self.run_build(*k, **kw)
  509. except self.errors.ConfigurationError:
  510. self.end_msg(kw['errmsg'], 'YELLOW', **kw)
  511. if Logs.verbose > 1:
  512. raise
  513. else:
  514. self.fatal('The configuration failed')
  515. else:
  516. kw['success'] = ret
  517. if kw.get('post_check'):
  518. ret = kw['post_check'](kw)
  519. if ret:
  520. self.end_msg(kw['errmsg'], 'YELLOW', **kw)
  521. self.fatal('The configuration failed %r' % ret)
  522. else:
  523. self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
  524. return ret