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