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.

627 lines
16KB

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