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.

657 lines
19KB

  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. self.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. env.launch_dir = Context.launch_dir
  142. if not (self.env.NO_LOCK_IN_RUN or env.environ.get('NO_LOCK_IN_RUN') or getattr(Options.options, 'no_lock_in_run')):
  143. env.store(os.path.join(Context.run_dir, Options.lockfile))
  144. if not (self.env.NO_LOCK_IN_TOP or env.environ.get('NO_LOCK_IN_TOP') or getattr(Options.options, 'no_lock_in_top')):
  145. env.store(os.path.join(Context.top_dir, Options.lockfile))
  146. if not (self.env.NO_LOCK_IN_OUT or env.environ.get('NO_LOCK_IN_OUT') or getattr(Options.options, 'no_lock_in_out')):
  147. env.store(os.path.join(Context.out_dir, Options.lockfile))
  148. def prepare_env(self, env):
  149. """
  150. Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``
  151. :type env: :py:class:`waflib.ConfigSet.ConfigSet`
  152. :param env: a ConfigSet, usually ``conf.env``
  153. """
  154. if not env.PREFIX:
  155. if Options.options.prefix or Utils.is_win32:
  156. env.PREFIX = Options.options.prefix
  157. else:
  158. env.PREFIX = '/'
  159. if not env.BINDIR:
  160. if Options.options.bindir:
  161. env.BINDIR = Options.options.bindir
  162. else:
  163. env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env)
  164. if not env.LIBDIR:
  165. if Options.options.libdir:
  166. env.LIBDIR = Options.options.libdir
  167. else:
  168. env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env)
  169. def store(self):
  170. """Save the config results into the cache file"""
  171. n = self.cachedir.make_node('build.config.py')
  172. n.write('version = 0x%x\ntools = %r\n' % (Context.HEXVERSION, self.tools))
  173. if not self.all_envs:
  174. self.fatal('nothing to store in the configuration context!')
  175. for key in self.all_envs:
  176. tmpenv = self.all_envs[key]
  177. tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX))
  178. def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False):
  179. """
  180. Load Waf tools, which will be imported whenever a build is started.
  181. :param tool_list: waf tools to import
  182. :type tool_list: list of string
  183. :param tooldir: paths for the imports
  184. :type tooldir: list of string
  185. :param funs: functions to execute from the waf tools
  186. :type funs: list of string
  187. :param cache: whether to prevent the tool from running twice
  188. :type cache: bool
  189. """
  190. tools = Utils.to_list(tool_list)
  191. if tooldir:
  192. tooldir = Utils.to_list(tooldir)
  193. for tool in tools:
  194. # avoid loading the same tool more than once with the same functions
  195. # used by composite projects
  196. if cache:
  197. mag = (tool, id(self.env), tooldir, funs)
  198. if mag in self.tool_cache:
  199. self.to_log('(tool %s is already loaded, skipping)' % tool)
  200. continue
  201. self.tool_cache.append(mag)
  202. module = None
  203. try:
  204. module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path)
  205. except ImportError as e:
  206. self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e))
  207. except Exception as e:
  208. self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs))
  209. self.to_log(traceback.format_exc())
  210. raise
  211. if funs is not None:
  212. self.eval_rules(funs)
  213. else:
  214. func = getattr(module, 'configure', None)
  215. if func:
  216. if type(func) is type(Utils.readf):
  217. func(self)
  218. else:
  219. self.eval_rules(func)
  220. self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})
  221. def post_recurse(self, node):
  222. """
  223. Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`
  224. :param node: script
  225. :type node: :py:class:`waflib.Node.Node`
  226. """
  227. super(ConfigurationContext, self).post_recurse(node)
  228. self.hash = Utils.h_list((self.hash, node.read('rb')))
  229. self.files.append(node.abspath())
  230. def eval_rules(self, rules):
  231. """
  232. Execute configuration tests provided as list of functions to run
  233. :param rules: list of configuration method names
  234. :type rules: list of string
  235. """
  236. self.rules = Utils.to_list(rules)
  237. for x in self.rules:
  238. f = getattr(self, x)
  239. if not f:
  240. self.fatal('No such configuration function %r' % x)
  241. f()
  242. def conf(f):
  243. """
  244. Decorator: attach new configuration functions to :py:class:`waflib.Build.BuildContext` and
  245. :py:class:`waflib.Configure.ConfigurationContext`. The methods bound will accept a parameter
  246. named 'mandatory' to disable the configuration errors::
  247. def configure(conf):
  248. conf.find_program('abc', mandatory=False)
  249. :param f: method to bind
  250. :type f: function
  251. """
  252. def fun(*k, **kw):
  253. mandatory = kw.pop('mandatory', True)
  254. try:
  255. return f(*k, **kw)
  256. except Errors.ConfigurationError:
  257. if mandatory:
  258. raise
  259. fun.__name__ = f.__name__
  260. setattr(ConfigurationContext, f.__name__, fun)
  261. setattr(Build.BuildContext, f.__name__, fun)
  262. return f
  263. @conf
  264. def add_os_flags(self, var, dest=None, dup=False):
  265. """
  266. Import operating system environment values into ``conf.env`` dict::
  267. def configure(conf):
  268. conf.add_os_flags('CFLAGS')
  269. :param var: variable to use
  270. :type var: string
  271. :param dest: destination variable, by default the same as var
  272. :type dest: string
  273. :param dup: add the same set of flags again
  274. :type dup: bool
  275. """
  276. try:
  277. flags = shlex.split(self.environ[var])
  278. except KeyError:
  279. return
  280. if dup or ''.join(flags) not in ''.join(Utils.to_list(self.env[dest or var])):
  281. self.env.append_value(dest or var, flags)
  282. @conf
  283. def cmd_to_list(self, cmd):
  284. """
  285. Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.
  286. :param cmd: command
  287. :type cmd: a string or a list of string
  288. """
  289. if isinstance(cmd, str):
  290. if os.path.isfile(cmd):
  291. # do not take any risk
  292. return [cmd]
  293. if os.sep == '/':
  294. return shlex.split(cmd)
  295. else:
  296. try:
  297. return shlex.split(cmd, posix=False)
  298. except TypeError:
  299. # Python 2.5 on windows?
  300. return shlex.split(cmd)
  301. return cmd
  302. @conf
  303. def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw):
  304. """
  305. Raise a Configuration error if the Waf version does not strictly match the given bounds::
  306. conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
  307. :type mini: number, tuple or string
  308. :param mini: Minimum required version
  309. :type maxi: number, tuple or string
  310. :param maxi: Maximum allowed version
  311. """
  312. self.start_msg('Checking for waf version in %s-%s' % (str(mini), str(maxi)), **kw)
  313. ver = Context.HEXVERSION
  314. if Utils.num2ver(mini) > ver:
  315. self.fatal('waf version should be at least %r (%r found)' % (Utils.num2ver(mini), ver))
  316. if Utils.num2ver(maxi) < ver:
  317. self.fatal('waf version should be at most %r (%r found)' % (Utils.num2ver(maxi), ver))
  318. self.end_msg('ok', **kw)
  319. @conf
  320. def find_file(self, filename, path_list=[]):
  321. """
  322. Find a file in a list of paths
  323. :param filename: name of the file to search for
  324. :param path_list: list of directories to search
  325. :return: the first matching filename; else a configuration exception is raised
  326. """
  327. for n in Utils.to_list(filename):
  328. for d in Utils.to_list(path_list):
  329. p = os.path.expanduser(os.path.join(d, n))
  330. if os.path.exists(p):
  331. return p
  332. self.fatal('Could not find %r' % filename)
  333. @conf
  334. def find_program(self, filename, **kw):
  335. """
  336. Search for a program on the operating system
  337. When var is used, you may set os.environ[var] to help find a specific program version, for example::
  338. $ CC='ccache gcc' waf configure
  339. :param path_list: paths to use for searching
  340. :type param_list: list of string
  341. :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
  342. :type var: string
  343. :param value: obtain the program from the value passed exclusively
  344. :type value: list or string (list is preferred)
  345. :param exts: list of extensions for the binary (do not add an extension for portability)
  346. :type exts: list of string
  347. :param msg: name to display in the log, by default filename is used
  348. :type msg: string
  349. :param interpreter: interpreter for the program
  350. :type interpreter: ConfigSet variable key
  351. :raises: :py:class:`waflib.Errors.ConfigurationError`
  352. """
  353. exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
  354. environ = kw.get('environ', getattr(self, 'environ', os.environ))
  355. ret = ''
  356. filename = Utils.to_list(filename)
  357. msg = kw.get('msg', ', '.join(filename))
  358. var = kw.get('var', '')
  359. if not var:
  360. var = re.sub(r'\W', '_', filename[0].upper())
  361. path_list = kw.get('path_list', '')
  362. if path_list:
  363. path_list = Utils.to_list(path_list)
  364. else:
  365. path_list = environ.get('PATH', '').split(os.pathsep)
  366. if kw.get('value'):
  367. # user-provided in command-line options and passed to find_program
  368. ret = self.cmd_to_list(kw['value'])
  369. elif environ.get(var):
  370. # user-provided in the os environment
  371. ret = self.cmd_to_list(environ[var])
  372. elif self.env[var]:
  373. # a default option in the wscript file
  374. ret = self.cmd_to_list(self.env[var])
  375. else:
  376. if not ret:
  377. ret = self.find_binary(filename, exts.split(','), path_list)
  378. if not ret and Utils.winreg:
  379. ret = Utils.get_registry_app_path(Utils.winreg.HKEY_CURRENT_USER, filename)
  380. if not ret and Utils.winreg:
  381. ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename)
  382. ret = self.cmd_to_list(ret)
  383. if ret:
  384. if len(ret) == 1:
  385. retmsg = ret[0]
  386. else:
  387. retmsg = ret
  388. else:
  389. retmsg = False
  390. self.msg('Checking for program %r' % msg, retmsg, **kw)
  391. if not kw.get('quiet'):
  392. self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret))
  393. if not ret:
  394. self.fatal(kw.get('errmsg', '') or 'Could not find the program %r' % filename)
  395. interpreter = kw.get('interpreter')
  396. if interpreter is None:
  397. if not Utils.check_exe(ret[0], env=environ):
  398. self.fatal('Program %r is not executable' % ret)
  399. self.env[var] = ret
  400. else:
  401. self.env[var] = self.env[interpreter] + ret
  402. return ret
  403. @conf
  404. def find_binary(self, filenames, exts, paths):
  405. for f in filenames:
  406. for ext in exts:
  407. exe_name = f + ext
  408. if os.path.isabs(exe_name):
  409. if os.path.isfile(exe_name):
  410. return exe_name
  411. else:
  412. for path in paths:
  413. x = os.path.expanduser(os.path.join(path, exe_name))
  414. if os.path.isfile(x):
  415. return x
  416. return None
  417. @conf
  418. def run_build(self, *k, **kw):
  419. """
  420. Create a temporary build context to execute a build. A temporary reference to that build
  421. context is kept on self.test_bld for debugging purposes.
  422. The arguments to this function are passed to a single task generator for that build.
  423. Only three parameters are mandatory:
  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: input file contents
  429. :type code: string
  430. Though this function returns *0* by default, the build may bind 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. The temporary builds creates a temporary folder; the name of that folder is calculated
  433. by hashing input arguments to this function, with the exception of :py:class:`waflib.ConfigSet.ConfigSet`
  434. objects which are used for both reading and writing values.
  435. This function also features a cache which is disabled by default; that cache relies
  436. on the hash value calculated as indicated above::
  437. def options(opt):
  438. opt.add_option('--confcache', dest='confcache', default=0,
  439. action='count', help='Use a configuration cache')
  440. And execute the configuration with the following command-line::
  441. $ waf configure --confcache
  442. """
  443. buf = []
  444. for key in sorted(kw.keys()):
  445. v = kw[key]
  446. if isinstance(v, ConfigSet.ConfigSet):
  447. # values are being written to, so they are excluded from contributing to the hash
  448. continue
  449. elif hasattr(v, '__call__'):
  450. buf.append(Utils.h_fun(v))
  451. else:
  452. buf.append(str(v))
  453. h = Utils.h_list(buf)
  454. dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
  455. cachemode = kw.get('confcache', getattr(Options.options, 'confcache', None))
  456. if not cachemode and os.path.exists(dir):
  457. shutil.rmtree(dir)
  458. try:
  459. os.makedirs(dir)
  460. except OSError:
  461. pass
  462. try:
  463. os.stat(dir)
  464. except OSError:
  465. self.fatal('cannot use the configuration test folder %r' % dir)
  466. if cachemode == 1:
  467. try:
  468. proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build'))
  469. except EnvironmentError:
  470. pass
  471. else:
  472. ret = proj['cache_run_build']
  473. if isinstance(ret, str) and ret.startswith('Test does not build'):
  474. self.fatal(ret)
  475. return ret
  476. bdir = os.path.join(dir, 'testbuild')
  477. if not os.path.exists(bdir):
  478. os.makedirs(bdir)
  479. cls_name = kw.get('run_build_cls') or getattr(self, 'run_build_cls', 'build')
  480. self.test_bld = bld = Context.create_context(cls_name, 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. bld.kw = kw
  488. bld.conf = self
  489. kw['build_fun'](bld)
  490. ret = -1
  491. try:
  492. try:
  493. bld.compile()
  494. except Errors.WafError:
  495. ret = 'Test does not build: %s' % traceback.format_exc()
  496. self.fatal(ret)
  497. else:
  498. ret = getattr(bld, 'retval', 0)
  499. finally:
  500. if cachemode:
  501. # cache the results each time
  502. proj = ConfigSet.ConfigSet()
  503. proj['cache_run_build'] = ret
  504. proj.store(os.path.join(dir, 'cache_run_build'))
  505. else:
  506. shutil.rmtree(dir)
  507. return ret
  508. @conf
  509. def ret_msg(self, msg, args):
  510. if isinstance(msg, str):
  511. return msg
  512. return msg(args)
  513. @conf
  514. def test(self, *k, **kw):
  515. if not 'env' in kw:
  516. kw['env'] = self.env.derive()
  517. # validate_c for example
  518. if kw.get('validate'):
  519. kw['validate'](kw)
  520. self.start_msg(kw['msg'], **kw)
  521. ret = None
  522. try:
  523. ret = self.run_build(*k, **kw)
  524. except self.errors.ConfigurationError:
  525. self.end_msg(kw['errmsg'], 'YELLOW', **kw)
  526. if Logs.verbose > 1:
  527. raise
  528. else:
  529. self.fatal('The configuration failed')
  530. else:
  531. kw['success'] = ret
  532. if kw.get('post_check'):
  533. ret = kw['post_check'](kw)
  534. if ret:
  535. self.end_msg(kw['errmsg'], 'YELLOW', **kw)
  536. self.fatal('The configuration failed %r' % ret)
  537. else:
  538. self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
  539. return ret