mesonmain.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # Copyright 2012-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 sys, stat, traceback, argparse
  12. import datetime
  13. import os.path
  14. import platform
  15. import cProfile as profile
  16. from . import environment, interpreter, mesonlib
  17. from . import build
  18. from . import mlog, coredata
  19. from .mesonlib import MesonException
  20. from .environment import detect_msys2_arch
  21. from .wrap import WrapMode
  22. default_warning = '1'
  23. def create_parser():
  24. p = argparse.ArgumentParser(prog='meson')
  25. coredata.register_builtin_arguments(p)
  26. p.add_argument('--cross-file', default=None,
  27. help='File describing cross compilation environment.')
  28. p.add_argument('-v', '--version', action='version',
  29. version=coredata.version)
  30. # See the mesonlib.WrapMode enum for documentation
  31. p.add_argument('--wrap-mode', default=WrapMode.default,
  32. type=wrapmodetype, choices=WrapMode,
  33. help='Special wrap mode to use')
  34. p.add_argument('--profile-self', action='store_true', dest='profile',
  35. help=argparse.SUPPRESS)
  36. p.add_argument('builddir', nargs='?', default='..')
  37. p.add_argument('sourcedir', nargs='?', default='.')
  38. return p
  39. def wrapmodetype(string):
  40. try:
  41. return getattr(WrapMode, string)
  42. except AttributeError:
  43. msg = ', '.join([t.name.lower() for t in WrapMode])
  44. msg = 'invalid argument {!r}, use one of {}'.format(string, msg)
  45. raise argparse.ArgumentTypeError(msg)
  46. class MesonApp:
  47. def __init__(self, dir1, dir2, handshake, options):
  48. (self.source_dir, self.build_dir) = self.validate_dirs(dir1, dir2, handshake)
  49. self.options = options
  50. def has_build_file(self, dirname):
  51. fname = os.path.join(dirname, environment.build_filename)
  52. return os.path.exists(fname)
  53. def validate_core_dirs(self, dir1, dir2):
  54. ndir1 = os.path.abspath(os.path.realpath(dir1))
  55. ndir2 = os.path.abspath(os.path.realpath(dir2))
  56. if not os.path.exists(ndir1):
  57. os.makedirs(ndir1)
  58. if not os.path.exists(ndir2):
  59. os.makedirs(ndir2)
  60. if not stat.S_ISDIR(os.stat(ndir1).st_mode):
  61. raise RuntimeError('%s is not a directory' % dir1)
  62. if not stat.S_ISDIR(os.stat(ndir2).st_mode):
  63. raise RuntimeError('%s is not a directory' % dir2)
  64. if os.path.samefile(dir1, dir2):
  65. raise RuntimeError('Source and build directories must not be the same. Create a pristine build directory.')
  66. if self.has_build_file(ndir1):
  67. if self.has_build_file(ndir2):
  68. raise RuntimeError('Both directories contain a build file %s.' % environment.build_filename)
  69. return ndir1, ndir2
  70. if self.has_build_file(ndir2):
  71. return ndir2, ndir1
  72. raise RuntimeError('Neither directory contains a build file %s.' % environment.build_filename)
  73. def validate_dirs(self, dir1, dir2, handshake):
  74. (src_dir, build_dir) = self.validate_core_dirs(dir1, dir2)
  75. priv_dir = os.path.join(build_dir, 'meson-private/coredata.dat')
  76. if os.path.exists(priv_dir):
  77. if not handshake:
  78. print('Directory already configured, exiting Meson. Just run your build command\n'
  79. '(e.g. ninja) and Meson will regenerate as necessary. If ninja fails, run ninja\n'
  80. 'reconfigure to force Meson to regenerate.\n'
  81. '\nIf build failures persist, manually wipe your build directory to clear any\n'
  82. 'stored system data.\n'
  83. '\nTo change option values, run meson configure instead.')
  84. sys.exit(0)
  85. else:
  86. if handshake:
  87. raise RuntimeError('Something went terribly wrong. Please file a bug.')
  88. return src_dir, build_dir
  89. def check_pkgconfig_envvar(self, env):
  90. curvar = os.environ.get('PKG_CONFIG_PATH', '')
  91. if curvar != env.coredata.pkgconf_envvar:
  92. mlog.warning('PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' %
  93. (env.coredata.pkgconf_envvar, curvar))
  94. env.coredata.pkgconf_envvar = curvar
  95. def generate(self):
  96. env = environment.Environment(self.source_dir, self.build_dir, self.options)
  97. mlog.initialize(env.get_log_dir())
  98. with mesonlib.BuildDirLock(self.build_dir):
  99. self._generate(env)
  100. def _generate(self, env):
  101. mlog.debug('Build started at', datetime.datetime.now().isoformat())
  102. mlog.debug('Main binary:', sys.executable)
  103. mlog.debug('Python system:', platform.system())
  104. mlog.log(mlog.bold('The Meson build system'))
  105. self.check_pkgconfig_envvar(env)
  106. mlog.log('Version:', coredata.version)
  107. mlog.log('Source dir:', mlog.bold(self.source_dir))
  108. mlog.log('Build dir:', mlog.bold(self.build_dir))
  109. if env.is_cross_build():
  110. mlog.log('Build type:', mlog.bold('cross build'))
  111. else:
  112. mlog.log('Build type:', mlog.bold('native build'))
  113. b = build.Build(env)
  114. intr = interpreter.Interpreter(b)
  115. if env.is_cross_build():
  116. mlog.log('Host machine cpu family:', mlog.bold(intr.builtin['host_machine'].cpu_family_method([], {})))
  117. mlog.log('Host machine cpu:', mlog.bold(intr.builtin['host_machine'].cpu_method([], {})))
  118. mlog.log('Target machine cpu family:', mlog.bold(intr.builtin['target_machine'].cpu_family_method([], {})))
  119. mlog.log('Target machine cpu:', mlog.bold(intr.builtin['target_machine'].cpu_method([], {})))
  120. mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {})))
  121. mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {})))
  122. if self.options.profile:
  123. fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log')
  124. profile.runctx('intr.run()', globals(), locals(), filename=fname)
  125. else:
  126. intr.run()
  127. # Print all default option values that don't match the current value
  128. for def_opt_name, def_opt_value, cur_opt_value in intr.get_non_matching_default_options():
  129. mlog.log('Option', mlog.bold(def_opt_name), 'is:',
  130. mlog.bold(str(cur_opt_value)),
  131. '[default: {}]'.format(str(def_opt_value)))
  132. try:
  133. dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat')
  134. # We would like to write coredata as late as possible since we use the existence of
  135. # this file to check if we generated the build file successfully. Since coredata
  136. # includes settings, the build files must depend on it and appear newer. However, due
  137. # to various kernel caches, we cannot guarantee that any time in Python is exactly in
  138. # sync with the time that gets applied to any files. Thus, we dump this file as late as
  139. # possible, but before build files, and if any error occurs, delete it.
  140. cdf = env.dump_coredata()
  141. if self.options.profile:
  142. fname = 'profile-{}-backend.log'.format(intr.backend.name)
  143. fname = os.path.join(self.build_dir, 'meson-private', fname)
  144. profile.runctx('intr.backend.generate(intr)', globals(), locals(), filename=fname)
  145. else:
  146. intr.backend.generate(intr)
  147. build.save(b, dumpfile)
  148. # Post-conf scripts must be run after writing coredata or else introspection fails.
  149. intr.backend.run_postconf_scripts()
  150. except:
  151. if 'cdf' in locals():
  152. old_cdf = cdf + '.prev'
  153. if os.path.exists(old_cdf):
  154. os.replace(old_cdf, cdf)
  155. else:
  156. os.unlink(cdf)
  157. raise
  158. def run_script_command(args):
  159. cmdname = args[0]
  160. cmdargs = args[1:]
  161. if cmdname == 'exe':
  162. import mesonbuild.scripts.meson_exe as abc
  163. cmdfunc = abc.run
  164. elif cmdname == 'cleantrees':
  165. import mesonbuild.scripts.cleantrees as abc
  166. cmdfunc = abc.run
  167. elif cmdname == 'commandrunner':
  168. import mesonbuild.scripts.commandrunner as abc
  169. cmdfunc = abc.run
  170. elif cmdname == 'delsuffix':
  171. import mesonbuild.scripts.delwithsuffix as abc
  172. cmdfunc = abc.run
  173. elif cmdname == 'dirchanger':
  174. import mesonbuild.scripts.dirchanger as abc
  175. cmdfunc = abc.run
  176. elif cmdname == 'gtkdoc':
  177. import mesonbuild.scripts.gtkdochelper as abc
  178. cmdfunc = abc.run
  179. elif cmdname == 'msgfmthelper':
  180. import mesonbuild.scripts.msgfmthelper as abc
  181. cmdfunc = abc.run
  182. elif cmdname == 'regencheck':
  183. import mesonbuild.scripts.regen_checker as abc
  184. cmdfunc = abc.run
  185. elif cmdname == 'symbolextractor':
  186. import mesonbuild.scripts.symbolextractor as abc
  187. cmdfunc = abc.run
  188. elif cmdname == 'scanbuild':
  189. import mesonbuild.scripts.scanbuild as abc
  190. cmdfunc = abc.run
  191. elif cmdname == 'vcstagger':
  192. import mesonbuild.scripts.vcstagger as abc
  193. cmdfunc = abc.run
  194. elif cmdname == 'gettext':
  195. import mesonbuild.scripts.gettext as abc
  196. cmdfunc = abc.run
  197. elif cmdname == 'yelphelper':
  198. import mesonbuild.scripts.yelphelper as abc
  199. cmdfunc = abc.run
  200. elif cmdname == 'uninstall':
  201. import mesonbuild.scripts.uninstall as abc
  202. cmdfunc = abc.run
  203. elif cmdname == 'dist':
  204. import mesonbuild.scripts.dist as abc
  205. cmdfunc = abc.run
  206. elif cmdname == 'coverage':
  207. import mesonbuild.scripts.coverage as abc
  208. cmdfunc = abc.run
  209. else:
  210. raise MesonException('Unknown internal command {}.'.format(cmdname))
  211. return cmdfunc(cmdargs)
  212. def set_meson_command(mainfile):
  213. if mainfile.endswith('.exe'):
  214. mesonlib.meson_command = [mainfile]
  215. elif os.path.isabs(mainfile) and mainfile.endswith('mesonmain.py'):
  216. # Can't actually run meson with an absolute path to mesonmain.py, it must be run as -m mesonbuild.mesonmain
  217. mesonlib.meson_command = mesonlib.python_command + ['-m', 'mesonbuild.mesonmain']
  218. else:
  219. mesonlib.meson_command = mesonlib.python_command + [mainfile]
  220. # This won't go into the log file because it's not initialized yet, and we
  221. # need this value for unit tests.
  222. if 'MESON_COMMAND_TESTS' in os.environ:
  223. mlog.log('meson_command is {!r}'.format(mesonlib.meson_command))
  224. def run(original_args, mainfile):
  225. if sys.version_info < (3, 5):
  226. print('Meson works correctly only with python 3.5+.')
  227. print('You have python %s.' % sys.version)
  228. print('Please update your environment')
  229. return 1
  230. # https://github.com/mesonbuild/meson/issues/3653
  231. if sys.platform.lower() == 'msys':
  232. mlog.error('This python3 seems to be msys/python on MSYS2 Windows, which is known to have path semantics incompatible with Meson')
  233. msys2_arch = detect_msys2_arch()
  234. if msys2_arch:
  235. mlog.error('Please install and use mingw-w64-i686-python3 and/or mingw-w64-x86_64-python3 with Pacman')
  236. else:
  237. mlog.error('Please download and use Python as detailed at: https://mesonbuild.com/Getting-meson.html')
  238. return 2
  239. # Set the meson command that will be used to run scripts and so on
  240. set_meson_command(mainfile)
  241. args = original_args[:]
  242. if len(args) > 0:
  243. # First check if we want to run a subcommand.
  244. cmd_name = args[0]
  245. remaining_args = args[1:]
  246. # "help" is a special case: Since printing of the help may be
  247. # delegated to a subcommand, we edit cmd_name before executing
  248. # the rest of the logic here.
  249. if cmd_name == 'help':
  250. remaining_args += ['--help']
  251. args = remaining_args
  252. cmd_name = args[0]
  253. if cmd_name == 'test':
  254. from . import mtest
  255. return mtest.run(remaining_args)
  256. elif cmd_name == 'setup':
  257. args = remaining_args
  258. # FALLTHROUGH like it's 1972.
  259. elif cmd_name == 'install':
  260. from . import minstall
  261. return minstall.run(remaining_args)
  262. elif cmd_name == 'introspect':
  263. from . import mintro
  264. return mintro.run(remaining_args)
  265. elif cmd_name == 'rewrite':
  266. from . import rewriter
  267. return rewriter.run(remaining_args)
  268. elif cmd_name == 'configure':
  269. try:
  270. from . import mconf
  271. return mconf.run(remaining_args)
  272. except MesonException as e:
  273. mlog.exception(e)
  274. sys.exit(1)
  275. elif cmd_name == 'wrap':
  276. from .wrap import wraptool
  277. return wraptool.run(remaining_args)
  278. elif cmd_name == 'init':
  279. from . import minit
  280. return minit.run(remaining_args)
  281. elif cmd_name == 'runpython':
  282. import runpy
  283. script_file = remaining_args[0]
  284. sys.argv[1:] = remaining_args[1:]
  285. runpy.run_path(script_file, run_name='__main__')
  286. sys.exit(0)
  287. # No special command? Do the basic setup/reconf.
  288. if len(args) >= 2 and args[0] == '--internal':
  289. if args[1] != 'regenerate':
  290. script = args[1]
  291. try:
  292. sys.exit(run_script_command(args[1:]))
  293. except MesonException as e:
  294. mlog.error('\nError in {} helper script:'.format(script))
  295. mlog.exception(e)
  296. sys.exit(1)
  297. args = args[2:]
  298. handshake = True
  299. else:
  300. handshake = False
  301. parser = create_parser()
  302. args = mesonlib.expand_arguments(args)
  303. options = parser.parse_args(args)
  304. coredata.parse_cmd_line_options(options)
  305. dir1 = options.builddir
  306. dir2 = options.sourcedir
  307. try:
  308. app = MesonApp(dir1, dir2, handshake, options)
  309. except Exception as e:
  310. # Log directory does not exist, so just print
  311. # to stdout.
  312. print('Error during basic setup:\n')
  313. print(e)
  314. return 1
  315. try:
  316. app.generate()
  317. except Exception as e:
  318. if isinstance(e, MesonException):
  319. mlog.exception(e)
  320. # Path to log file
  321. mlog.shutdown()
  322. logfile = os.path.join(app.build_dir, environment.Environment.log_dir, mlog.log_fname)
  323. mlog.log("\nA full log can be found at", mlog.bold(logfile))
  324. if os.environ.get('MESON_FORCE_BACKTRACE'):
  325. raise
  326. return 1
  327. else:
  328. if os.environ.get('MESON_FORCE_BACKTRACE'):
  329. raise
  330. traceback.print_exc()
  331. return 2
  332. finally:
  333. mlog.shutdown()
  334. return 0
  335. def main():
  336. # Always resolve the command path so Ninja can find it for regen, tests, etc.
  337. if 'meson.exe' in sys.executable:
  338. assert(os.path.isabs(sys.executable))
  339. launcher = sys.executable
  340. else:
  341. launcher = os.path.realpath(sys.argv[0])
  342. return run(sys.argv[1:], launcher)
  343. if __name__ == '__main__':
  344. sys.exit(main())