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.

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