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.

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