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