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.

637 lines
17KB

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