jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

666 lines
18KB

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