mconf.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. # Copyright 2014-2016 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. import os
  12. from . import coredata, environment, mesonlib, build, mintro, mlog
  13. from .ast import AstIDGenerator
  14. def add_arguments(parser):
  15. coredata.register_builtin_arguments(parser)
  16. parser.add_argument('builddir', nargs='?', default='.')
  17. parser.add_argument('--clearcache', action='store_true', default=False,
  18. help='Clear cached state (e.g. found dependencies)')
  19. def make_lower_case(val):
  20. if isinstance(val, bool):
  21. return str(val).lower()
  22. elif isinstance(val, list):
  23. return [make_lower_case(i) for i in val]
  24. else:
  25. return str(val)
  26. class ConfException(mesonlib.MesonException):
  27. pass
  28. class Conf:
  29. def __init__(self, build_dir):
  30. self.build_dir = os.path.abspath(os.path.realpath(build_dir))
  31. if 'meson.build' in [os.path.basename(self.build_dir), self.build_dir]:
  32. self.build_dir = os.path.dirname(self.build_dir)
  33. self.build = None
  34. self.max_choices_line_length = 60
  35. self.name_col = []
  36. self.value_col = []
  37. self.choices_col = []
  38. self.descr_col = []
  39. self.has_choices = False
  40. self.all_subprojects = set()
  41. self.yielding_options = set()
  42. if os.path.isdir(os.path.join(self.build_dir, 'meson-private')):
  43. self.build = build.load(self.build_dir)
  44. self.source_dir = self.build.environment.get_source_dir()
  45. self.coredata = coredata.load(self.build_dir)
  46. self.default_values_only = False
  47. elif os.path.isfile(os.path.join(self.build_dir, environment.build_filename)):
  48. # Make sure that log entries in other parts of meson don't interfere with the JSON output
  49. mlog.disable()
  50. self.source_dir = os.path.abspath(os.path.realpath(self.build_dir))
  51. intr = mintro.IntrospectionInterpreter(self.source_dir, '', 'ninja', visitors = [AstIDGenerator()])
  52. intr.analyze()
  53. # Re-enable logging just in case
  54. mlog.enable()
  55. self.coredata = intr.coredata
  56. self.default_values_only = True
  57. else:
  58. raise ConfException('Directory {} is neither a Meson build directory nor a project source directory.'.format(build_dir))
  59. def clear_cache(self):
  60. self.coredata.deps.host.clear()
  61. self.coredata.deps.build.clear()
  62. def set_options(self, options):
  63. self.coredata.set_options(options)
  64. def save(self):
  65. # Do nothing when using introspection
  66. if self.default_values_only:
  67. return
  68. # Only called if something has changed so overwrite unconditionally.
  69. coredata.save(self.coredata, self.build_dir)
  70. # We don't write the build file because any changes to it
  71. # are erased when Meson is executed the next time, i.e. when
  72. # Ninja is run.
  73. def print_aligned(self):
  74. col_widths = (max([len(i) for i in self.name_col], default=0),
  75. max([len(i) for i in self.value_col], default=0),
  76. max([len(i) for i in self.choices_col], default=0))
  77. for line in zip(self.name_col, self.value_col, self.choices_col, self.descr_col):
  78. if self.has_choices:
  79. print('{0:{width[0]}} {1:{width[1]}} {2:{width[2]}} {3}'.format(*line, width=col_widths))
  80. else:
  81. print('{0:{width[0]}} {1:{width[1]}} {3}'.format(*line, width=col_widths))
  82. def split_options_per_subproject(self, options_iter):
  83. result = {}
  84. for k, o in options_iter:
  85. subproject = ''
  86. if ':' in k:
  87. subproject, optname = k.split(':')
  88. if o.yielding and optname in options:
  89. self.yielding_options.add(k)
  90. self.all_subprojects.add(subproject)
  91. result.setdefault(subproject, {})[k] = o
  92. return result
  93. def _add_line(self, name, value, choices, descr):
  94. self.name_col.append(' ' * self.print_margin + name)
  95. self.value_col.append(value)
  96. self.choices_col.append(choices)
  97. self.descr_col.append(descr)
  98. def add_option(self, name, descr, value, choices):
  99. if isinstance(value, list):
  100. value = '[{0}]'.format(', '.join(make_lower_case(value)))
  101. else:
  102. value = make_lower_case(value)
  103. if choices:
  104. self.has_choices = True
  105. if isinstance(choices, list):
  106. choices_list = make_lower_case(choices)
  107. current = '['
  108. while choices_list:
  109. i = choices_list.pop(0)
  110. if len(current) + len(i) >= self.max_choices_line_length:
  111. self._add_line(name, value, current + ',', descr)
  112. name = ''
  113. value = ''
  114. descr = ''
  115. current = ' '
  116. if len(current) > 1:
  117. current += ', '
  118. current += i
  119. choices = current + ']'
  120. else:
  121. choices = make_lower_case(choices)
  122. else:
  123. choices = ''
  124. self._add_line(name, value, choices, descr)
  125. def add_title(self, title):
  126. titles = {'descr': 'Description', 'value': 'Current Value', 'choices': 'Possible Values'}
  127. if self.default_values_only:
  128. titles['value'] = 'Default Value'
  129. self._add_line('', '', '', '')
  130. self._add_line(title, titles['value'], titles['choices'], titles['descr'])
  131. self._add_line('-' * len(title), '-' * len(titles['value']), '-' * len(titles['choices']), '-' * len(titles['descr']))
  132. def add_section(self, section):
  133. self.print_margin = 0
  134. self._add_line('', '', '', '')
  135. self._add_line(section + ':', '', '', '')
  136. self.print_margin = 2
  137. def print_options(self, title, options):
  138. if not options:
  139. return
  140. if title:
  141. self.add_title(title)
  142. for k, o in sorted(options.items()):
  143. printable_value = o.printable_value()
  144. if k in self.yielding_options:
  145. printable_value = '<inherited from main project>'
  146. self.add_option(k, o.description, printable_value, o.choices)
  147. def print_conf(self):
  148. def print_default_values_warning():
  149. mlog.warning('The source directory instead of the build directory was specified.')
  150. mlog.warning('Only the default values for the project are printed, and all command line parameters are ignored.')
  151. if self.default_values_only:
  152. print_default_values_warning()
  153. print('')
  154. print('Core properties:')
  155. print(' Source dir', self.source_dir)
  156. if not self.default_values_only:
  157. print(' Build dir ', self.build_dir)
  158. dir_option_names = ['bindir',
  159. 'datadir',
  160. 'includedir',
  161. 'infodir',
  162. 'libdir',
  163. 'libexecdir',
  164. 'localedir',
  165. 'localstatedir',
  166. 'mandir',
  167. 'prefix',
  168. 'sbindir',
  169. 'sharedstatedir',
  170. 'sysconfdir']
  171. test_option_names = ['errorlogs',
  172. 'stdsplit']
  173. core_option_names = [k for k in self.coredata.builtins if k not in dir_option_names + test_option_names]
  174. dir_options = {k: o for k, o in self.coredata.builtins.items() if k in dir_option_names}
  175. test_options = {k: o for k, o in self.coredata.builtins.items() if k in test_option_names}
  176. core_options = {k: o for k, o in self.coredata.builtins.items() if k in core_option_names}
  177. def insert_build_prefix(k):
  178. idx = k.find(':')
  179. if idx < 0:
  180. return 'build.' + k
  181. return k[:idx + 1] + 'build.' + k[idx + 1:]
  182. core_options = self.split_options_per_subproject(core_options.items())
  183. host_compiler_options = self.split_options_per_subproject(
  184. self.coredata.flatten_lang_iterator(
  185. self.coredata.compiler_options.host.items()))
  186. build_compiler_options = self.split_options_per_subproject(
  187. self.coredata.flatten_lang_iterator(
  188. (insert_build_prefix(k), o)
  189. for k, o in self.coredata.compiler_options.build.items()))
  190. project_options = self.split_options_per_subproject(self.coredata.user_options.items())
  191. show_build_options = self.default_values_only or self.build.environment.is_cross_build()
  192. self.add_section('Main project options')
  193. self.print_options('Core options', core_options[''])
  194. self.print_options('', self.coredata.builtins_per_machine.host)
  195. if show_build_options:
  196. self.print_options('', {insert_build_prefix(k): o for k, o in self.coredata.builtins_per_machine.build.items()})
  197. self.print_options('Backend options', self.coredata.backend_options)
  198. self.print_options('Base options', self.coredata.base_options)
  199. self.print_options('Compiler options', host_compiler_options.get('', {}))
  200. if show_build_options:
  201. self.print_options('', build_compiler_options.get('', {}))
  202. self.print_options('Directories', dir_options)
  203. self.print_options('Testing options', test_options)
  204. self.print_options('Project options', project_options.get('', {}))
  205. for subproject in sorted(self.all_subprojects):
  206. if subproject == '':
  207. continue
  208. self.add_section('Subproject ' + subproject)
  209. if subproject in core_options:
  210. self.print_options('Core options', core_options[subproject])
  211. if subproject in host_compiler_options:
  212. self.print_options('Compiler options', host_compiler_options[subproject])
  213. if subproject in build_compiler_options and show_build_options:
  214. self.print_options('', build_compiler_options[subproject])
  215. if subproject in project_options:
  216. self.print_options('Project options', project_options[subproject])
  217. self.print_aligned()
  218. # Print the warning twice so that the user shouldn't be able to miss it
  219. if self.default_values_only:
  220. print('')
  221. print_default_values_warning()
  222. def run(options):
  223. coredata.parse_cmd_line_options(options)
  224. builddir = os.path.abspath(os.path.realpath(options.builddir))
  225. c = None
  226. try:
  227. c = Conf(builddir)
  228. if c.default_values_only:
  229. c.print_conf()
  230. return 0
  231. save = False
  232. if len(options.cmd_line_options) > 0:
  233. c.set_options(options.cmd_line_options)
  234. coredata.update_cmd_line_file(builddir, options)
  235. save = True
  236. elif options.clearcache:
  237. c.clear_cache()
  238. save = True
  239. else:
  240. c.print_conf()
  241. if save:
  242. c.save()
  243. mintro.update_build_options(c.coredata, c.build.environment.info_dir)
  244. mintro.write_meson_info_file(c.build, [])
  245. except ConfException as e:
  246. print('Meson configurator encountered an error:')
  247. if c is not None and c.build is not None:
  248. mintro.write_meson_info_file(c.build, [e])
  249. raise e
  250. return 0