lib/util: do an early return on error directory_create_or_exist()
[Samba.git] / buildtools / wafadmin / Scripting.py
blobd975bd934d13c793a2efe2c38941d511226c64d7
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005 (ita)
5 "Module called for configuring, compiling and installing targets"
7 import os, sys, shutil, traceback, datetime, inspect, errno
9 import Utils, Configure, Build, Logs, Options, Environment, Task
10 from Logs import error, warn, info
11 from Constants import *
13 g_gz = 'bz2'
14 commands = []
16 def prepare_impl(t, cwd, ver, wafdir):
17 Options.tooldir = [t]
18 Options.launch_dir = cwd
20 # some command-line options can be processed immediately
21 if '--version' in sys.argv:
22 opt_obj = Options.Handler()
23 opt_obj.curdir = cwd
24 opt_obj.parse_args()
25 sys.exit(0)
27 # now find the wscript file
28 msg1 = 'Waf: Please run waf from a directory containing a file named "%s" or run distclean' % WSCRIPT_FILE
30 # in theory projects can be configured in an autotool-like manner:
31 # mkdir build && cd build && ../waf configure && ../waf
32 build_dir_override = None
33 candidate = None
35 lst = os.listdir(cwd)
37 search_for_candidate = True
38 if WSCRIPT_FILE in lst:
39 candidate = cwd
41 elif 'configure' in sys.argv and not WSCRIPT_BUILD_FILE in lst:
42 # autotool-like configuration
43 calldir = os.path.abspath(os.path.dirname(sys.argv[0]))
44 if WSCRIPT_FILE in os.listdir(calldir):
45 candidate = calldir
46 search_for_candidate = False
47 else:
48 error('arg[0] directory does not contain a wscript file')
49 sys.exit(1)
50 build_dir_override = cwd
52 # climb up to find a script if it is not found
53 while search_for_candidate:
54 if len(cwd) <= 3:
55 break # stop at / or c:
56 dirlst = os.listdir(cwd)
57 if WSCRIPT_FILE in dirlst:
58 candidate = cwd
59 if 'configure' in sys.argv and candidate:
60 break
61 if Options.lockfile in dirlst:
62 env = Environment.Environment()
63 try:
64 env.load(os.path.join(cwd, Options.lockfile))
65 except:
66 error('could not load %r' % Options.lockfile)
67 try:
68 os.stat(env['cwd'])
69 except:
70 candidate = cwd
71 else:
72 candidate = env['cwd']
73 break
74 cwd = os.path.dirname(cwd) # climb up
76 if not candidate:
77 # check if the user only wanted to display the help
78 if '-h' in sys.argv or '--help' in sys.argv:
79 warn('No wscript file found: the help message may be incomplete')
80 opt_obj = Options.Handler()
81 opt_obj.curdir = cwd
82 opt_obj.parse_args()
83 else:
84 error(msg1)
85 sys.exit(0)
87 # We have found wscript, but there is no guarantee that it is valid
88 try:
89 os.chdir(candidate)
90 except OSError:
91 raise Utils.WafError("the folder %r is unreadable" % candidate)
93 # define the main module containing the functions init, shutdown, ..
94 Utils.set_main_module(os.path.join(candidate, WSCRIPT_FILE))
96 if build_dir_override:
97 d = getattr(Utils.g_module, BLDDIR, None)
98 if d:
99 # test if user has set the blddir in wscript.
100 msg = ' Overriding build directory %s with %s' % (d, build_dir_override)
101 warn(msg)
102 Utils.g_module.blddir = build_dir_override
104 # bind a few methods and classes by default
106 def set_def(obj, name=''):
107 n = name or obj.__name__
108 if not n in Utils.g_module.__dict__:
109 setattr(Utils.g_module, n, obj)
111 for k in [dist, distclean, distcheck, clean, install, uninstall]:
112 set_def(k)
114 set_def(Configure.ConfigurationContext, 'configure_context')
116 for k in ['build', 'clean', 'install', 'uninstall']:
117 set_def(Build.BuildContext, k + '_context')
119 # now parse the options from the user wscript file
120 opt_obj = Options.Handler(Utils.g_module)
121 opt_obj.curdir = candidate
122 try:
123 f = Utils.g_module.set_options
124 except AttributeError:
125 pass
126 else:
127 opt_obj.sub_options([''])
128 opt_obj.parse_args()
130 if not 'init' in Utils.g_module.__dict__:
131 Utils.g_module.init = Utils.nada
132 if not 'shutdown' in Utils.g_module.__dict__:
133 Utils.g_module.shutdown = Utils.nada
135 main()
137 def prepare(t, cwd, ver, wafdir):
138 if WAFVERSION != ver:
139 msg = 'Version mismatch: waf %s <> wafadmin %s (wafdir %s)' % (ver, WAFVERSION, wafdir)
140 print('\033[91mError: %s\033[0m' % msg)
141 sys.exit(1)
143 #"""
144 try:
145 prepare_impl(t, cwd, ver, wafdir)
146 except Utils.WafError, e:
147 error(str(e))
148 sys.exit(1)
149 except KeyboardInterrupt:
150 Utils.pprint('RED', 'Interrupted')
151 sys.exit(68)
153 import cProfile, pstats
154 cProfile.runctx("import Scripting; Scripting.prepare_impl(t, cwd, ver, wafdir)", {},
155 {'t': t, 'cwd':cwd, 'ver':ver, 'wafdir':wafdir},
156 'profi.txt')
157 p = pstats.Stats('profi.txt')
158 p.sort_stats('time').print_stats(45)
159 #"""
161 def main():
162 global commands
163 commands = Options.arg_line[:]
165 while commands:
166 x = commands.pop(0)
168 ini = datetime.datetime.now()
169 if x == 'configure':
170 fun = configure
171 elif x == 'build':
172 fun = build
173 else:
174 fun = getattr(Utils.g_module, x, None)
176 if not fun:
177 raise Utils.WscriptError('No such command %r' % x)
179 ctx = getattr(Utils.g_module, x + '_context', Utils.Context)()
181 if x in ['init', 'shutdown', 'dist', 'distclean', 'distcheck']:
182 # compatibility TODO remove in waf 1.6
183 try:
184 fun(ctx)
185 except TypeError:
186 fun()
187 else:
188 fun(ctx)
190 ela = ''
191 if not Options.options.progress_bar:
192 ela = ' (%s)' % Utils.get_elapsed_time(ini)
194 if x != 'init' and x != 'shutdown':
195 info('%r finished successfully%s' % (x, ela))
197 if not commands and x != 'shutdown':
198 commands.append('shutdown')
200 def configure(conf):
202 src = getattr(Options.options, SRCDIR, None)
203 if not src: src = getattr(Utils.g_module, SRCDIR, None)
204 if not src: src = getattr(Utils.g_module, 'top', None)
205 if not src:
206 src = '.'
207 incomplete_src = 1
208 src = os.path.abspath(src)
210 bld = getattr(Options.options, BLDDIR, None)
211 if not bld: bld = getattr(Utils.g_module, BLDDIR, None)
212 if not bld: bld = getattr(Utils.g_module, 'out', None)
213 if not bld:
214 bld = 'build'
215 incomplete_bld = 1
216 if bld == '.':
217 raise Utils.WafError('Setting blddir="." may cause distclean problems')
218 bld = os.path.abspath(bld)
220 try: os.makedirs(bld)
221 except OSError: pass
223 # It is not possible to compile specific targets in the configuration
224 # this may cause configuration errors if autoconfig is set
225 targets = Options.options.compile_targets
226 Options.options.compile_targets = None
227 Options.is_install = False
229 conf.srcdir = src
230 conf.blddir = bld
231 conf.post_init()
233 if 'incomplete_src' in vars():
234 conf.check_message_1('Setting srcdir to')
235 conf.check_message_2(src)
236 if 'incomplete_bld' in vars():
237 conf.check_message_1('Setting blddir to')
238 conf.check_message_2(bld)
240 # calling to main wscript's configure()
241 conf.sub_config([''])
243 conf.store()
245 # this will write a configure lock so that subsequent builds will
246 # consider the current path as the root directory (see prepare_impl).
247 # to remove: use 'waf distclean'
248 env = Environment.Environment()
249 env[BLDDIR] = bld
250 env[SRCDIR] = src
251 env['argv'] = sys.argv
252 env['commands'] = Options.commands
253 env['options'] = Options.options.__dict__
255 # conf.hash & conf.files hold wscript files paths and hash
256 # (used only by Configure.autoconfig)
257 env['hash'] = conf.hash
258 env['files'] = conf.files
259 env['environ'] = dict(conf.environ)
260 env['cwd'] = os.path.split(Utils.g_module.root_path)[0]
262 if Utils.g_module.root_path != src:
263 # in case the source dir is somewhere else
264 env.store(os.path.join(src, Options.lockfile))
266 env.store(Options.lockfile)
268 Options.options.compile_targets = targets
270 def clean(bld):
271 '''removes the build files'''
272 try:
273 proj = Environment.Environment(Options.lockfile)
274 except IOError:
275 raise Utils.WafError('Nothing to clean (project not configured)')
277 bld.load_dirs(proj[SRCDIR], proj[BLDDIR])
278 bld.load_envs()
280 bld.is_install = 0 # False
282 # read the scripts - and set the path to the wscript path (useful for srcdir='/foo/bar')
283 bld.add_subdirs([os.path.split(Utils.g_module.root_path)[0]])
285 try:
286 bld.clean()
287 finally:
288 bld.save()
290 def check_configured(bld):
291 if not Configure.autoconfig:
292 return bld
294 conf_cls = getattr(Utils.g_module, 'configure_context', Utils.Context)
295 bld_cls = getattr(Utils.g_module, 'build_context', Utils.Context)
297 def reconf(proj):
298 back = (Options.commands, Options.options.__dict__, Logs.zones, Logs.verbose)
300 Options.commands = proj['commands']
301 Options.options.__dict__ = proj['options']
302 conf = conf_cls()
303 conf.environ = proj['environ']
304 configure(conf)
306 (Options.commands, Options.options.__dict__, Logs.zones, Logs.verbose) = back
308 try:
309 proj = Environment.Environment(Options.lockfile)
310 except IOError:
311 conf = conf_cls()
312 configure(conf)
313 else:
314 try:
315 bld = bld_cls()
316 bld.load_dirs(proj[SRCDIR], proj[BLDDIR])
317 bld.load_envs()
318 except Utils.WafError:
319 reconf(proj)
320 return bld_cls()
322 try:
323 proj = Environment.Environment(Options.lockfile)
324 except IOError:
325 raise Utils.WafError('Auto-config: project does not configure (bug)')
327 h = 0
328 try:
329 for file in proj['files']:
330 if file.endswith('configure'):
331 h = hash((h, Utils.readf(file)))
332 else:
333 mod = Utils.load_module(file)
334 h = hash((h, mod.waf_hash_val))
335 except (OSError, IOError):
336 warn('Reconfiguring the project: a file is unavailable')
337 reconf(proj)
338 else:
339 if (h != proj['hash']):
340 warn('Reconfiguring the project: the configuration has changed')
341 reconf(proj)
343 return bld_cls()
345 def install(bld):
346 '''installs the build files'''
347 bld = check_configured(bld)
349 Options.commands['install'] = True
350 Options.commands['uninstall'] = False
351 Options.is_install = True
353 bld.is_install = INSTALL
355 build_impl(bld)
356 bld.install()
358 def uninstall(bld):
359 '''removes the installed files'''
360 Options.commands['install'] = False
361 Options.commands['uninstall'] = True
362 Options.is_install = True
364 bld.is_install = UNINSTALL
366 try:
367 def runnable_status(self):
368 return SKIP_ME
369 setattr(Task.Task, 'runnable_status_back', Task.Task.runnable_status)
370 setattr(Task.Task, 'runnable_status', runnable_status)
372 build_impl(bld)
373 bld.install()
374 finally:
375 setattr(Task.Task, 'runnable_status', Task.Task.runnable_status_back)
377 def build(bld):
378 bld = check_configured(bld)
380 Options.commands['install'] = False
381 Options.commands['uninstall'] = False
382 Options.is_install = False
384 bld.is_install = 0 # False
386 return build_impl(bld)
388 def build_impl(bld):
389 # compile the project and/or install the files
390 try:
391 proj = Environment.Environment(Options.lockfile)
392 except IOError:
393 raise Utils.WafError("Project not configured (run 'waf configure' first)")
395 bld.load_dirs(proj[SRCDIR], proj[BLDDIR])
396 bld.load_envs()
398 info("Waf: Entering directory `%s'" % bld.bldnode.abspath())
399 bld.add_subdirs([os.path.split(Utils.g_module.root_path)[0]])
401 # execute something immediately before the build starts
402 bld.pre_build()
404 try:
405 bld.compile()
406 finally:
407 if Options.options.progress_bar: print('')
408 info("Waf: Leaving directory `%s'" % bld.bldnode.abspath())
410 # execute something immediately after a successful build
411 bld.post_build()
413 bld.install()
415 excludes = '.bzr .bzrignore .git .gitignore .svn CVS .cvsignore .arch-ids {arch} SCCS BitKeeper .hg _MTN _darcs Makefile Makefile.in config.log .gitattributes .hgignore .hgtags'.split()
416 dist_exts = '~ .rej .orig .pyc .pyo .bak .tar.bz2 tar.gz .zip .swp'.split()
417 def dont_dist(name, src, build_dir):
418 global excludes, dist_exts
420 if (name.startswith(',,')
421 or name.startswith('++')
422 or name.startswith('.waf')
423 or (src == '.' and name == Options.lockfile)
424 or name in excludes
425 or name == build_dir
427 return True
429 for ext in dist_exts:
430 if name.endswith(ext):
431 return True
433 return False
435 # like shutil.copytree
436 # exclude files and to raise exceptions immediately
437 def copytree(src, dst, build_dir):
438 names = os.listdir(src)
439 os.makedirs(dst)
440 for name in names:
441 srcname = os.path.join(src, name)
442 dstname = os.path.join(dst, name)
444 if dont_dist(name, src, build_dir):
445 continue
447 if os.path.isdir(srcname):
448 copytree(srcname, dstname, build_dir)
449 else:
450 shutil.copy2(srcname, dstname)
452 # TODO in waf 1.6, change this method if "srcdir == blddir" is allowed
453 def distclean(ctx=None):
454 '''removes the build directory'''
455 global commands
456 lst = os.listdir('.')
457 for f in lst:
458 if f == Options.lockfile:
459 try:
460 proj = Environment.Environment(f)
461 except:
462 Logs.warn('could not read %r' % f)
463 continue
465 try:
466 shutil.rmtree(proj[BLDDIR])
467 except IOError:
468 pass
469 except OSError, e:
470 if e.errno != errno.ENOENT:
471 Logs.warn('project %r cannot be removed' % proj[BLDDIR])
473 try:
474 os.remove(f)
475 except OSError, e:
476 if e.errno != errno.ENOENT:
477 Logs.warn('file %r cannot be removed' % f)
479 # remove the local waf cache
480 if not commands and f.startswith('.waf'):
481 shutil.rmtree(f, ignore_errors=True)
483 # FIXME waf 1.6 a unique ctx parameter, and remove the optional appname and version
484 def dist(appname='', version=''):
485 '''makes a tarball for redistributing the sources'''
486 # return return (distdirname, tarballname)
487 import tarfile
489 if not appname: appname = Utils.g_module.APPNAME
490 if not version: version = Utils.g_module.VERSION
492 tmp_folder = appname + '-' + version
493 if g_gz in ['gz', 'bz2']:
494 arch_name = tmp_folder + '.tar.' + g_gz
495 else:
496 arch_name = tmp_folder + '.' + 'zip'
498 # remove the previous dir
499 try:
500 shutil.rmtree(tmp_folder)
501 except (OSError, IOError):
502 pass
504 # remove the previous archive
505 try:
506 os.remove(arch_name)
507 except (OSError, IOError):
508 pass
510 # copy the files into the temporary folder
511 blddir = getattr(Utils.g_module, BLDDIR, None)
512 if not blddir:
513 blddir = getattr(Utils.g_module, 'out', None)
514 copytree('.', tmp_folder, blddir)
516 # undocumented hook for additional cleanup
517 dist_hook = getattr(Utils.g_module, 'dist_hook', None)
518 if dist_hook:
519 back = os.getcwd()
520 os.chdir(tmp_folder)
521 try:
522 dist_hook()
523 finally:
524 # go back to the root directory
525 os.chdir(back)
527 if g_gz in ['gz', 'bz2']:
528 tar = tarfile.open(arch_name, 'w:' + g_gz)
529 tar.add(tmp_folder)
530 tar.close()
531 else:
532 Utils.zip_folder(tmp_folder, arch_name, tmp_folder)
534 try: from hashlib import sha1 as sha
535 except ImportError: from sha import sha
536 try:
537 digest = " (sha=%r)" % sha(Utils.readf(arch_name)).hexdigest()
538 except:
539 digest = ''
541 info('New archive created: %s%s' % (arch_name, digest))
543 if os.path.exists(tmp_folder): shutil.rmtree(tmp_folder)
544 return arch_name
546 # FIXME waf 1.6 a unique ctx parameter, and remove the optional appname and version
547 def distcheck(appname='', version='', subdir=''):
548 '''checks if the sources compile (tarball from 'dist')'''
549 import tempfile, tarfile
551 if not appname: appname = Utils.g_module.APPNAME
552 if not version: version = Utils.g_module.VERSION
554 waf = os.path.abspath(sys.argv[0])
555 tarball = dist(appname, version)
557 path = appname + '-' + version
559 # remove any previous instance
560 if os.path.exists(path):
561 shutil.rmtree(path)
563 t = tarfile.open(tarball)
564 for x in t: t.extract(x)
565 t.close()
567 # build_path is the directory for the waf invocation
568 if subdir:
569 build_path = os.path.join(path, subdir)
570 else:
571 build_path = path
573 instdir = tempfile.mkdtemp('.inst', '%s-%s' % (appname, version))
574 ret = Utils.pproc.Popen([waf, 'configure', 'build', 'install', 'uninstall', '--destdir=' + instdir], cwd=build_path).wait()
575 if ret:
576 raise Utils.WafError('distcheck failed with code %i' % ret)
578 if os.path.exists(instdir):
579 raise Utils.WafError('distcheck succeeded, but files were left in %s' % instdir)
581 shutil.rmtree(path)
583 # FIXME remove in Waf 1.6 (kept for compatibility)
584 def add_subdir(dir, bld):
585 bld.recurse(dir, 'build')