Scripting.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2018 (ita)
  4. "Module called for configuring, compiling and installing targets"
  5. import os, shlex, shutil, traceback, errno, sys, stat
  6. from waflib import Utils, Configure, Logs, Options, ConfigSet, Context, Errors, Build, Node
  7. build_dir_override = None
  8. no_climb_commands = ['configure']
  9. default_cmd = "build"
  10. def waf_entry_point(current_directory, version, wafdir):
  11. """
  12. This is the main entry point, all Waf execution starts here.
  13. :param current_directory: absolute path representing the current directory
  14. :type current_directory: string
  15. :param version: version number
  16. :type version: string
  17. :param wafdir: absolute path representing the directory of the waf library
  18. :type wafdir: string
  19. """
  20. Logs.init_log()
  21. if Context.WAFVERSION != version:
  22. Logs.error('Waf script %r and library %r do not match (directory %r)', version, Context.WAFVERSION, wafdir)
  23. sys.exit(1)
  24. # Store current directory before any chdir
  25. Context.waf_dir = wafdir
  26. Context.run_dir = Context.launch_dir = current_directory
  27. start_dir = current_directory
  28. no_climb = os.environ.get('NOCLIMB')
  29. if len(sys.argv) > 1:
  30. # os.path.join handles absolute paths
  31. # if sys.argv[1] is not an absolute path, then it is relative to the current working directory
  32. potential_wscript = os.path.join(current_directory, sys.argv[1])
  33. if os.path.basename(potential_wscript) == Context.WSCRIPT_FILE and os.path.isfile(potential_wscript):
  34. # need to explicitly normalize the path, as it may contain extra '/.'
  35. path = os.path.normpath(os.path.dirname(potential_wscript))
  36. start_dir = os.path.abspath(path)
  37. no_climb = True
  38. sys.argv.pop(1)
  39. ctx = Context.create_context('options')
  40. # allow --ver option in user scripts #2453
  41. ctx.parser.allow_abbrev = False
  42. (options, commands) = ctx.parse_cmd_args(allow_unknown=True)
  43. if options.version:
  44. print('%s %s (%s)'%(Context.WAFNAME, Context.WAFVERSION, Context.WAFREVISION))
  45. sys.exit(0)
  46. if getattr(options, 'top', None):
  47. start_dir = Context.run_dir = Context.top_dir = options.top
  48. no_climb = True
  49. if getattr(options, 'out', None):
  50. Context.out_dir = options.out
  51. # if 'configure' is in the commands, do not search any further
  52. if not no_climb:
  53. for k in no_climb_commands:
  54. for y in commands:
  55. if y.startswith(k):
  56. no_climb = True
  57. break
  58. # try to find a lock file (if the project was configured)
  59. # at the same time, store the first wscript file seen
  60. cur = start_dir
  61. while cur:
  62. try:
  63. lst = os.listdir(cur)
  64. except OSError:
  65. lst = []
  66. Logs.error('Directory %r is unreadable!', cur)
  67. if Options.lockfile in lst:
  68. env = ConfigSet.ConfigSet()
  69. try:
  70. env.load(os.path.join(cur, Options.lockfile))
  71. ino = os.stat(cur)[stat.ST_INO]
  72. except EnvironmentError:
  73. pass
  74. else:
  75. # check if the folder was not moved
  76. for x in (env.run_dir, env.top_dir, env.out_dir):
  77. if not x:
  78. continue
  79. if Utils.is_win32:
  80. if cur == x:
  81. load = True
  82. break
  83. else:
  84. # if the filesystem features symlinks, compare the inode numbers
  85. try:
  86. ino2 = os.stat(x)[stat.ST_INO]
  87. except OSError:
  88. pass
  89. else:
  90. if ino == ino2:
  91. load = True
  92. break
  93. else:
  94. Logs.warn('invalid lock file in %s', cur)
  95. load = False
  96. if load:
  97. Context.run_dir = env.run_dir
  98. Context.top_dir = env.top_dir
  99. Context.out_dir = env.out_dir
  100. break
  101. if not Context.run_dir:
  102. if Context.WSCRIPT_FILE in lst:
  103. Context.run_dir = cur
  104. next = os.path.dirname(cur)
  105. if next == cur:
  106. break
  107. cur = next
  108. if no_climb:
  109. break
  110. wscript = os.path.normpath(os.path.join(Context.run_dir, Context.WSCRIPT_FILE))
  111. if not os.path.exists(wscript):
  112. if options.whelp:
  113. Logs.warn('These are the generic options (no wscript/project found)')
  114. ctx.parser.print_help()
  115. sys.exit(0)
  116. Logs.error('Waf: Run from a folder containing a %r file (or try -h for the generic options)', Context.WSCRIPT_FILE)
  117. sys.exit(1)
  118. try:
  119. os.chdir(Context.run_dir)
  120. except OSError:
  121. Logs.error('Waf: The folder %r is unreadable', Context.run_dir)
  122. sys.exit(1)
  123. try:
  124. set_main_module(wscript)
  125. except Errors.WafError as e:
  126. Logs.pprint('RED', e.verbose_msg)
  127. Logs.error(str(e))
  128. sys.exit(1)
  129. except Exception as e:
  130. Logs.error('Waf: The wscript in %r is unreadable', Context.run_dir)
  131. traceback.print_exc(file=sys.stdout)
  132. sys.exit(2)
  133. if options.profile:
  134. import cProfile, pstats
  135. cProfile.runctx('from waflib import Scripting; Scripting.run_commands()', {}, {}, 'profi.txt')
  136. p = pstats.Stats('profi.txt')
  137. p.sort_stats('time').print_stats(75) # or 'cumulative'
  138. else:
  139. try:
  140. try:
  141. run_commands()
  142. except:
  143. if options.pdb:
  144. import pdb
  145. type, value, tb = sys.exc_info()
  146. traceback.print_exc()
  147. pdb.post_mortem(tb)
  148. else:
  149. raise
  150. except Errors.WafError as e:
  151. if Logs.verbose > 1:
  152. Logs.pprint('RED', e.verbose_msg)
  153. Logs.error(e.msg)
  154. sys.exit(1)
  155. except SystemExit:
  156. raise
  157. except Exception as e:
  158. traceback.print_exc(file=sys.stdout)
  159. sys.exit(2)
  160. except KeyboardInterrupt:
  161. Logs.pprint('RED', 'Interrupted')
  162. sys.exit(68)
  163. def set_main_module(file_path):
  164. """
  165. Read the main wscript file into :py:const:`waflib.Context.Context.g_module` and
  166. bind default functions such as ``init``, ``dist``, ``distclean`` if not defined.
  167. Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
  168. :param file_path: absolute path representing the top-level wscript file
  169. :type file_path: string
  170. """
  171. Context.g_module = Context.load_module(file_path)
  172. Context.g_module.root_path = file_path
  173. # note: to register the module globally, use the following:
  174. # sys.modules['wscript_main'] = g_module
  175. def set_def(obj):
  176. name = obj.__name__
  177. if not name in Context.g_module.__dict__:
  178. setattr(Context.g_module, name, obj)
  179. for k in (dist, distclean, distcheck):
  180. set_def(k)
  181. # add dummy init and shutdown functions if they're not defined
  182. if not 'init' in Context.g_module.__dict__:
  183. Context.g_module.init = Utils.nada
  184. if not 'shutdown' in Context.g_module.__dict__:
  185. Context.g_module.shutdown = Utils.nada
  186. if not 'options' in Context.g_module.__dict__:
  187. Context.g_module.options = Utils.nada
  188. def parse_options():
  189. """
  190. Parses the command-line options and initialize the logging system.
  191. Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
  192. """
  193. ctx = Context.create_context('options')
  194. ctx.execute()
  195. if not Options.commands:
  196. if isinstance(default_cmd, list):
  197. Options.commands.extend(default_cmd)
  198. else:
  199. Options.commands.append(default_cmd)
  200. if Options.options.whelp:
  201. ctx.parser.print_help()
  202. sys.exit(0)
  203. def run_command(cmd_name):
  204. """
  205. Executes a single Waf command. Called by :py:func:`waflib.Scripting.run_commands`.
  206. :param cmd_name: command to execute, like ``build``
  207. :type cmd_name: string
  208. """
  209. ctx = Context.create_context(cmd_name)
  210. ctx.log_timer = Utils.Timer()
  211. ctx.options = Options.options # provided for convenience
  212. ctx.cmd = cmd_name
  213. try:
  214. ctx.execute()
  215. finally:
  216. # Issue 1374
  217. ctx.finalize()
  218. return ctx
  219. def run_commands():
  220. """
  221. Execute the Waf commands that were given on the command-line, and the other options
  222. Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization, and executed
  223. after :py:func:`waflib.Scripting.parse_options`.
  224. """
  225. parse_options()
  226. run_command('init')
  227. while Options.commands:
  228. cmd_name = Options.commands.pop(0)
  229. ctx = run_command(cmd_name)
  230. Logs.info('%r finished successfully (%s)', cmd_name, ctx.log_timer)
  231. run_command('shutdown')
  232. ###########################################################################################
  233. def distclean_dir(dirname):
  234. """
  235. Distclean function called in the particular case when::
  236. top == out
  237. :param dirname: absolute path of the folder to clean
  238. :type dirname: string
  239. """
  240. for (root, dirs, files) in os.walk(dirname):
  241. for f in files:
  242. if f.endswith(('.o', '.moc', '.exe')):
  243. fname = os.path.join(root, f)
  244. try:
  245. os.remove(fname)
  246. except OSError:
  247. Logs.warn('Could not remove %r', fname)
  248. for x in (Context.DBFILE, 'config.log'):
  249. try:
  250. os.remove(x)
  251. except OSError:
  252. pass
  253. try:
  254. shutil.rmtree(Build.CACHE_DIR)
  255. except OSError:
  256. pass
  257. def distclean(ctx):
  258. '''removes build folders and data'''
  259. def remove_and_log(k, fun):
  260. try:
  261. fun(k)
  262. except EnvironmentError as e:
  263. if e.errno != errno.ENOENT:
  264. Logs.warn('Could not remove %r', k)
  265. # remove waf cache folders on the top-level
  266. if not Options.commands:
  267. for k in os.listdir('.'):
  268. for x in '.waf-2 waf-2 .waf3-2 waf3-2'.split():
  269. if k.startswith(x):
  270. remove_and_log(k, shutil.rmtree)
  271. # remove a build folder, if any
  272. cur = '.'
  273. if os.environ.get('NO_LOCK_IN_TOP') or ctx.options.no_lock_in_top:
  274. cur = ctx.options.out
  275. try:
  276. lst = os.listdir(cur)
  277. except OSError:
  278. Logs.warn('Could not read %r', cur)
  279. return
  280. if Options.lockfile in lst:
  281. f = os.path.join(cur, Options.lockfile)
  282. try:
  283. env = ConfigSet.ConfigSet(f)
  284. except EnvironmentError:
  285. Logs.warn('Could not read %r', f)
  286. return
  287. if not env.out_dir or not env.top_dir:
  288. Logs.warn('Invalid lock file %r', f)
  289. return
  290. if env.out_dir == env.top_dir:
  291. distclean_dir(env.out_dir)
  292. else:
  293. remove_and_log(env.out_dir, shutil.rmtree)
  294. env_dirs = [env.out_dir]
  295. if not (os.environ.get('NO_LOCK_IN_TOP') or ctx.options.no_lock_in_top):
  296. env_dirs.append(env.top_dir)
  297. if not (os.environ.get('NO_LOCK_IN_RUN') or ctx.options.no_lock_in_run):
  298. env_dirs.append(env.run_dir)
  299. for k in env_dirs:
  300. p = os.path.join(k, Options.lockfile)
  301. remove_and_log(p, os.remove)
  302. class Dist(Context.Context):
  303. '''creates an archive containing the project source code'''
  304. cmd = 'dist'
  305. fun = 'dist'
  306. algo = 'tar.bz2'
  307. ext_algo = {}
  308. def execute(self):
  309. """
  310. See :py:func:`waflib.Context.Context.execute`
  311. """
  312. self.recurse([os.path.dirname(Context.g_module.root_path)])
  313. self.archive()
  314. def archive(self):
  315. """
  316. Creates the source archive.
  317. """
  318. import tarfile
  319. arch_name = self.get_arch_name()
  320. try:
  321. self.base_path
  322. except AttributeError:
  323. self.base_path = self.path
  324. node = self.base_path.make_node(arch_name)
  325. try:
  326. node.delete()
  327. except OSError:
  328. pass
  329. files = self.get_files()
  330. if self.algo.startswith('tar.'):
  331. tar = tarfile.open(node.abspath(), 'w:' + self.algo.replace('tar.', ''))
  332. for x in files:
  333. self.add_tar_file(x, tar)
  334. tar.close()
  335. elif self.algo == 'zip':
  336. import zipfile
  337. zip = zipfile.ZipFile(node.abspath(), 'w', compression=zipfile.ZIP_DEFLATED)
  338. for x in files:
  339. archive_name = self.get_base_name() + '/' + x.path_from(self.base_path)
  340. if os.environ.get('SOURCE_DATE_EPOCH'):
  341. # TODO: parse that timestamp
  342. zip.writestr(zipfile.ZipInfo(archive_name), x.read(), zipfile.ZIP_DEFLATED)
  343. else:
  344. zip.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)
  345. zip.close()
  346. else:
  347. self.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
  348. try:
  349. from hashlib import sha256
  350. except ImportError:
  351. digest = ''
  352. else:
  353. digest = ' (sha256=%r)' % sha256(node.read(flags='rb')).hexdigest()
  354. Logs.info('New archive created: %s%s', self.arch_name, digest)
  355. def get_tar_path(self, node):
  356. """
  357. Return the path to use for a node in the tar archive, the purpose of this
  358. is to let subclases resolve symbolic links or to change file names
  359. :return: absolute path
  360. :rtype: string
  361. """
  362. return node.abspath()
  363. def add_tar_file(self, x, tar):
  364. """
  365. Adds a file to the tar archive. Symlinks are not verified.
  366. :param x: file path
  367. :param tar: tar file object
  368. """
  369. p = self.get_tar_path(x)
  370. tinfo = tar.gettarinfo(name=p, arcname=self.get_tar_prefix() + '/' + x.path_from(self.base_path))
  371. tinfo.uid = 0
  372. tinfo.gid = 0
  373. tinfo.uname = 'root'
  374. tinfo.gname = 'root'
  375. if os.environ.get('SOURCE_DATE_EPOCH'):
  376. tinfo.mtime = int(os.environ.get('SOURCE_DATE_EPOCH'))
  377. if os.path.isfile(p):
  378. with open(p, 'rb') as f:
  379. tar.addfile(tinfo, fileobj=f)
  380. else:
  381. tar.addfile(tinfo)
  382. def get_tar_prefix(self):
  383. """
  384. Returns the base path for files added into the archive tar file
  385. :rtype: string
  386. """
  387. try:
  388. return self.tar_prefix
  389. except AttributeError:
  390. return self.get_base_name()
  391. def get_arch_name(self):
  392. """
  393. Returns the archive file name.
  394. Set the attribute *arch_name* to change the default value::
  395. def dist(ctx):
  396. ctx.arch_name = 'ctx.tar.bz2'
  397. :rtype: string
  398. """
  399. try:
  400. self.arch_name
  401. except AttributeError:
  402. self.arch_name = self.get_base_name() + '.' + self.ext_algo.get(self.algo, self.algo)
  403. return self.arch_name
  404. def get_base_name(self):
  405. """
  406. Returns the default name of the main directory in the archive, which is set to *appname-version*.
  407. Set the attribute *base_name* to change the default value::
  408. def dist(ctx):
  409. ctx.base_name = 'files'
  410. :rtype: string
  411. """
  412. try:
  413. self.base_name
  414. except AttributeError:
  415. appname = getattr(Context.g_module, Context.APPNAME, 'noname')
  416. version = getattr(Context.g_module, Context.VERSION, '1.0')
  417. self.base_name = appname + '-' + version
  418. return self.base_name
  419. def get_excl(self):
  420. """
  421. Returns the patterns to exclude for finding the files in the top-level directory.
  422. Set the attribute *excl* to change the default value::
  423. def dist(ctx):
  424. ctx.excl = 'build **/*.o **/*.class'
  425. :rtype: string
  426. """
  427. try:
  428. return self.excl
  429. except AttributeError:
  430. self.excl = Node.exclude_regs + ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
  431. if Context.out_dir:
  432. nd = self.root.find_node(Context.out_dir)
  433. if nd:
  434. self.excl += ' ' + nd.path_from(self.base_path)
  435. return self.excl
  436. def get_files(self):
  437. """
  438. Files to package are searched automatically by :py:func:`waflib.Node.Node.ant_glob`.
  439. Set *files* to prevent this behaviour::
  440. def dist(ctx):
  441. ctx.files = ctx.path.find_node('wscript')
  442. Files are also searched from the directory 'base_path', to change it, set::
  443. def dist(ctx):
  444. ctx.base_path = path
  445. :rtype: list of :py:class:`waflib.Node.Node`
  446. """
  447. try:
  448. files = self.files
  449. except AttributeError:
  450. files = self.base_path.ant_glob('**/*', excl=self.get_excl())
  451. return files
  452. def dist(ctx):
  453. '''makes a tarball for redistributing the sources'''
  454. pass
  455. class DistCheck(Dist):
  456. """creates an archive with dist, then tries to build it"""
  457. fun = 'distcheck'
  458. cmd = 'distcheck'
  459. def execute(self):
  460. """
  461. See :py:func:`waflib.Context.Context.execute`
  462. """
  463. self.recurse([os.path.dirname(Context.g_module.root_path)])
  464. self.archive()
  465. self.check()
  466. def make_distcheck_cmd(self, tmpdir):
  467. cfg = []
  468. if Options.options.distcheck_args:
  469. cfg = shlex.split(Options.options.distcheck_args)
  470. else:
  471. cfg = [x for x in sys.argv if x.startswith('-')]
  472. cmd = [sys.executable, sys.argv[0], 'configure', 'build', 'install', 'uninstall', '--destdir=' + tmpdir] + cfg
  473. return cmd
  474. def check(self):
  475. """
  476. Creates the archive, uncompresses it and tries to build the project
  477. """
  478. import tempfile, tarfile
  479. with tarfile.open(self.get_arch_name()) as t:
  480. for x in t:
  481. if hasattr(tarfile, 'data_filter'):
  482. t.extract(x, filter='data')
  483. else:
  484. t.extract(x)
  485. instdir = tempfile.mkdtemp('.inst', self.get_base_name())
  486. cmd = self.make_distcheck_cmd(instdir)
  487. ret = Utils.subprocess.Popen(cmd, cwd=self.get_base_name()).wait()
  488. if ret:
  489. raise Errors.WafError('distcheck failed with code %r' % ret)
  490. if os.path.exists(instdir):
  491. raise Errors.WafError('distcheck succeeded, but files were left in %s' % instdir)
  492. shutil.rmtree(self.get_base_name())
  493. def distcheck(ctx):
  494. '''checks if the project compiles (tarball from 'dist')'''
  495. pass
  496. def autoconfigure(execute_method):
  497. """
  498. Decorator that enables context commands to run *configure* as needed.
  499. """
  500. def execute(self):
  501. """
  502. Wraps :py:func:`waflib.Context.Context.execute` on the context class
  503. """
  504. if not Configure.autoconfig:
  505. return execute_method(self)
  506. env = ConfigSet.ConfigSet()
  507. do_config = False
  508. try:
  509. env.load(os.path.join(Context.top_dir, Options.lockfile))
  510. except EnvironmentError:
  511. Logs.warn('Configuring the project')
  512. do_config = True
  513. else:
  514. if env.run_dir != Context.run_dir:
  515. do_config = True
  516. else:
  517. h = 0
  518. for f in env.files:
  519. try:
  520. h = Utils.h_list((h, Utils.readf(f, 'rb')))
  521. except EnvironmentError:
  522. do_config = True
  523. break
  524. else:
  525. do_config = h != env.hash
  526. if do_config:
  527. cmd = env.config_cmd or 'configure'
  528. if Configure.autoconfig == 'clobber':
  529. tmp = Options.options.__dict__
  530. launch_dir_tmp = Context.launch_dir
  531. if env.options:
  532. Options.options.__dict__ = env.options
  533. Context.launch_dir = env.launch_dir
  534. try:
  535. run_command(cmd)
  536. finally:
  537. Options.options.__dict__ = tmp
  538. Context.launch_dir = launch_dir_tmp
  539. else:
  540. run_command(cmd)
  541. run_command(self.cmd)
  542. else:
  543. return execute_method(self)
  544. return execute
  545. Build.BuildContext.execute = autoconfigure(Build.BuildContext.execute)