3
This file implements the main() function used by the scons script.
5
Architecturally, this *is* the scons script, and will likely only be
6
called from the external "scons" wrapper. Consequently, anything here
7
should not be, or be considered, part of the build engine. If it's
8
something that we expect other software to want to use, it should go in
9
some other module. If it's specific to the "scons" script invocation,
13
unsupported_python_version = (2, 3, 0)
14
deprecated_python_version = (2, 7, 0)
16
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation
18
# Permission is hereby granted, free of charge, to any person obtaining
19
# a copy of this software and associated documentation files (the
20
# "Software"), to deal in the Software without restriction, including
21
# without limitation the rights to use, copy, modify, merge, publish,
22
# distribute, sublicense, and/or sell copies of the Software, and to
23
# permit persons to whom the Software is furnished to do so, subject to
24
# the following conditions:
26
# The above copyright notice and this permission notice shall be included
27
# in all copies or substantial portions of the Software.
29
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
30
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
31
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37
__revision__ = "src/engine/SCons/Script/Main.py 2013/03/03 09:48:35 garyo"
46
# Strip the script directory from sys.path() so on case-insensitive
47
# (Windows) systems Python doesn't think that the "scons" script is the
48
# "SCons" package. Replace it with our own version directory so, if
49
# if they're there, we pick up the right version of the build engine
51
#sys.path = [os.path.join(sys.prefix,
53
# 'scons-%d' % SCons.__version__)] + sys.path[1:]
58
import SCons.Environment
66
import SCons.Taskmaster
70
import SCons.Script.Interactive
72
def fetch_win32_parallel_msg():
73
# A subsidiary function that exists solely to isolate this import
74
# so we don't have to pull it in on all platforms, and so that an
75
# in-line "import" statement in the _main() function below doesn't
76
# cause warnings about local names shadowing use of the 'SCons'
77
# globl in nest scopes and UnboundLocalErrors and the like in some
78
# versions (2.1) of Python.
79
import SCons.Platform.win32
80
return SCons.Platform.win32.parallel_msg
84
class SConsPrintHelpException(Exception):
87
display = SCons.Util.display
88
progress_display = SCons.Util.DisplayEngine()
90
first_command_start = None
91
last_command_end = None
93
class Progressor(object):
96
target_string = '$TARGET'
98
def __init__(self, obj, interval=1, file=None, overwrite=False):
104
self.interval = interval
105
self.overwrite = overwrite
109
elif SCons.Util.is_List(obj):
110
self.func = self.spinner
111
elif obj.find(self.target_string) != -1:
112
self.func = self.replace_string
114
self.func = self.string
121
def erase_previous(self):
123
length = len(self.prev)
124
if self.prev[-1] in ('\n', '\r'):
126
self.write(' ' * length + '\r')
129
def spinner(self, node):
130
self.write(self.obj[self.count % len(self.obj)])
132
def string(self, node):
135
def replace_string(self, node):
136
self.write(self.obj.replace(self.target_string, str(node)))
138
def __call__(self, node):
139
self.count = self.count + 1
140
if (self.count % self.interval) == 0:
142
self.erase_previous()
145
ProgressObject = SCons.Util.Null()
147
def Progress(*args, **kw):
148
global ProgressObject
149
ProgressObject = Progressor(*args, **kw)
156
def GetBuildFailures():
157
return _BuildFailures
159
class BuildTask(SCons.Taskmaster.OutOfDateTask):
160
"""An SCons build task."""
161
progress = ProgressObject
163
def display(self, message):
164
display('scons: ' + message)
167
self.progress(self.targets[0])
168
return SCons.Taskmaster.OutOfDateTask.prepare(self)
170
def needs_execute(self):
171
if SCons.Taskmaster.OutOfDateTask.needs_execute(self):
173
if self.top and self.targets[0].has_builder():
174
display("scons: `%s' is up to date." % str(self.node))
179
start_time = time.time()
180
global first_command_start
181
if first_command_start is None:
182
first_command_start = start_time
183
SCons.Taskmaster.OutOfDateTask.execute(self)
185
global cumulative_command_time
186
global last_command_end
187
finish_time = time.time()
188
last_command_end = finish_time
189
cumulative_command_time = cumulative_command_time+finish_time-start_time
190
sys.stdout.write("Command execution time: %s: %f seconds\n"%(str(self.node), finish_time-start_time))
192
def do_failed(self, status=2):
193
_BuildFailures.append(self.exception[1])
195
global this_build_status
196
if self.options.ignore_errors:
197
SCons.Taskmaster.OutOfDateTask.executed(self)
198
elif self.options.keep_going:
199
SCons.Taskmaster.OutOfDateTask.fail_continue(self)
201
this_build_status = status
203
SCons.Taskmaster.OutOfDateTask.fail_stop(self)
205
this_build_status = status
209
if self.top and not t.has_builder() and not t.side_effect:
211
if t.__class__.__name__ in ('File', 'Dir', 'Entry'):
212
errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.abspath)
213
else: # Alias or Python or ...
214
errstr="Do not know how to make %s target `%s'." % (t.__class__.__name__, t)
215
sys.stderr.write("scons: *** " + errstr)
216
if not self.options.keep_going:
217
sys.stderr.write(" Stop.")
218
sys.stderr.write("\n")
220
raise SCons.Errors.BuildError(t, errstr)
221
except KeyboardInterrupt:
227
print "scons: Nothing to be done for `%s'." % t
228
SCons.Taskmaster.OutOfDateTask.executed(self)
230
SCons.Taskmaster.OutOfDateTask.executed(self)
233
# Handle the failure of a build task. The primary purpose here
234
# is to display the various types of Errors and Exceptions
236
exc_info = self.exc_info()
244
# The Taskmaster didn't record an exception for this Task;
245
# see if the sys module has one.
247
t, e, tb = sys.exc_info()[:]
252
# Deprecated string exceptions will have their string stored
253
# in the first entry of the tuple.
257
buildError = SCons.Errors.convert_to_BuildError(e)
258
if not buildError.node:
259
buildError.node = self.node
261
node = buildError.node
262
if not SCons.Util.is_List(node):
264
nodename = ', '.join(map(str, node))
266
errfmt = "scons: *** [%s] %s\n"
267
sys.stderr.write(errfmt % (nodename, buildError))
269
if (buildError.exc_info[2] and buildError.exc_info[1] and
271
buildError.exc_info[1],
272
(EnvironmentError, SCons.Errors.StopError,
273
SCons.Errors.UserError))):
274
type, value, trace = buildError.exc_info
275
traceback.print_exception(type, value, trace)
276
elif tb and print_stacktrace:
277
sys.stderr.write("scons: internal stack trace:\n")
278
traceback.print_tb(tb, file=sys.stderr)
280
self.exception = (e, buildError, tb) # type, value, traceback
281
self.do_failed(buildError.exitstatus)
285
def postprocess(self):
288
for tp in self.options.tree_printers:
290
if self.options.debug_includes:
291
tree = t.render_include_tree()
295
SCons.Taskmaster.OutOfDateTask.postprocess(self)
297
def make_ready(self):
298
"""Make a task ready for execution"""
299
SCons.Taskmaster.OutOfDateTask.make_ready(self)
300
if self.out_of_date and self.options.debug_explain:
301
explanation = self.out_of_date[0].explain()
303
sys.stdout.write("scons: " + explanation)
305
class CleanTask(SCons.Taskmaster.AlwaysTask):
306
"""An SCons clean task."""
307
def fs_delete(self, path, pathstr, remove=1):
309
if os.path.lexists(path):
310
if os.path.isfile(path) or os.path.islink(path):
311
if remove: os.unlink(path)
312
display("Removed " + pathstr)
313
elif os.path.isdir(path) and not os.path.islink(path):
314
# delete everything in the dir
315
for e in sorted(os.listdir(path)):
316
p = os.path.join(path, e)
317
s = os.path.join(pathstr, e)
318
if os.path.isfile(p):
319
if remove: os.unlink(p)
320
display("Removed " + s)
322
self.fs_delete(p, s, remove)
323
# then delete dir itself
324
if remove: os.rmdir(path)
325
display("Removed directory " + pathstr)
327
errstr = "Path '%s' exists but isn't a file or directory."
328
raise SCons.Errors.UserError(errstr % (pathstr))
329
except SCons.Errors.UserError, e:
331
except (IOError, OSError), e:
332
print "scons: Could not remove '%s':" % pathstr, e.strerror
335
target = self.targets[0]
336
if (target.has_builder() or target.side_effect) and not target.noclean:
337
for t in self.targets:
339
display("Removed " + str(t))
340
if target in SCons.Environment.CleanTargets:
341
files = SCons.Environment.CleanTargets[target]
343
self.fs_delete(f.abspath, str(f), 0)
346
target = self.targets[0]
347
if (target.has_builder() or target.side_effect) and not target.noclean:
348
for t in self.targets:
352
# An OSError may indicate something like a permissions
353
# issue, an IOError would indicate something like
354
# the file not existing. In either case, print a
355
# message and keep going to try to remove as many
356
# targets aa possible.
357
print "scons: Could not remove '%s':" % str(t), e.strerror
360
display("Removed " + str(t))
361
if target in SCons.Environment.CleanTargets:
362
files = SCons.Environment.CleanTargets[target]
364
self.fs_delete(f.abspath, str(f))
368
# We want the Taskmaster to update the Node states (and therefore
369
# handle reference counts, etc.), but we don't want to call
370
# back to the Node's post-build methods, which would do things
371
# we don't want, like store .sconsign information.
372
executed = SCons.Taskmaster.Task.executed_without_callbacks
374
# Have the taskmaster arrange to "execute" all of the targets, because
375
# we'll figure out ourselves (in remove() or show() above) whether
376
# anything really needs to be done.
377
make_ready = SCons.Taskmaster.Task.make_ready_all
382
class QuestionTask(SCons.Taskmaster.AlwaysTask):
383
"""An SCons task for the -q (question) option."""
388
if self.targets[0].get_state() != SCons.Node.up_to_date or \
389
(self.top and not self.targets[0].exists()):
391
global this_build_status
393
this_build_status = 1
400
class TreePrinter(object):
401
def __init__(self, derived=False, prune=False, status=False):
402
self.derived = derived
405
def get_all_children(self, node):
406
return node.all_children()
407
def get_derived_children(self, node):
408
children = node.all_children(None)
409
return [x for x in children if x.has_builder()]
410
def display(self, t):
412
func = self.get_derived_children
414
func = self.get_all_children
415
s = self.status and 2 or 0
416
SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
419
def python_version_string():
420
return sys.version.split()[0]
422
def python_version_unsupported(version=sys.version_info):
423
return version < unsupported_python_version
425
def python_version_deprecated(version=sys.version_info):
426
return version < deprecated_python_version
436
cumulative_command_time = 0
437
exit_status = 0 # final exit status, assume success by default
438
this_build_status = 0 # "exit status" of an individual build
440
delayed_warnings = []
442
class FakeOptionParser(object):
444
A do-nothing option parser, used for the initial OptionsParser variable.
446
During normal SCons operation, the OptionsParser is created right
447
away by the main() function. Certain tests scripts however, can
448
introspect on different Tool modules, the initialization of which
449
can try to add a new, local option to an otherwise uninitialized
450
OptionsParser object. This allows that introspection to happen
454
class FakeOptionValues(object):
455
def __getattr__(self, attr):
457
values = FakeOptionValues()
458
def add_local_option(self, *args, **kw):
461
OptionsParser = FakeOptionParser()
463
def AddOption(*args, **kw):
464
if 'default' not in kw:
466
result = OptionsParser.add_local_option(*args, **kw)
470
return getattr(OptionsParser.values, name)
472
def SetOption(name, value):
473
return OptionsParser.values.set_option(name, value)
480
self.append = self.do_nothing
481
self.print_stats = self.do_nothing
482
def enable(self, outfp):
484
self.append = self.do_append
485
self.print_stats = self.do_print
486
def do_nothing(self, *args, **kw):
489
class CountStats(Stats):
490
def do_append(self, label):
491
self.labels.append(label)
492
self.stats.append(SCons.Debug.fetchLoggedInstances())
496
for n in [t[0] for t in s]:
497
stats_table[n] = [0, 0, 0, 0]
501
stats_table[n][i] = c
503
self.outfp.write("Object counts:\n")
507
fmt1 = ''.join(pre + [' %7s']*l + post)
508
fmt2 = ''.join(pre + [' %7d']*l + post)
509
labels = self.labels[:l]
510
labels.append(("", "Class"))
511
self.outfp.write(fmt1 % tuple([x[0] for x in labels]))
512
self.outfp.write(fmt1 % tuple([x[1] for x in labels]))
513
for k in sorted(stats_table.keys()):
514
r = stats_table[k][:l] + [k]
515
self.outfp.write(fmt2 % tuple(r))
517
count_stats = CountStats()
519
class MemStats(Stats):
520
def do_append(self, label):
521
self.labels.append(label)
522
self.stats.append(SCons.Debug.memory())
524
fmt = 'Memory %-32s %12d\n'
525
for label, stats in zip(self.labels, self.stats):
526
self.outfp.write(fmt % (label, stats))
528
memory_stats = MemStats()
532
def _scons_syntax_error(e):
533
"""Handle syntax errors. Print out a message and show where the error
536
etype, value, tb = sys.exc_info()
537
lines = traceback.format_exception_only(etype, value)
539
sys.stderr.write(line+'\n')
542
def find_deepest_user_frame(tb):
544
Find the deepest stack frame that is not part of SCons.
546
Input is a "pre-processed" stack trace in the form
547
returned by traceback.extract_tb() or traceback.extract_stack()
552
# find the deepest traceback frame that is not part
556
if filename.find(os.sep+'SCons'+os.sep) == -1:
560
def _scons_user_error(e):
561
"""Handle user errors. Print out a message and a description of the
562
error, along with the line number and routine where it occured.
563
The file and line number will be the deepest stack frame that is
564
not part of SCons itself.
566
global print_stacktrace
567
etype, value, tb = sys.exc_info()
569
traceback.print_exception(etype, value, tb)
570
filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
571
sys.stderr.write("\nscons: *** %s\n" % value)
572
sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
575
def _scons_user_warning(e):
576
"""Handle user warnings. Print out a message and a description of
577
the warning, along with the line number and routine where it occured.
578
The file and line number will be the deepest stack frame that is
579
not part of SCons itself.
581
etype, value, tb = sys.exc_info()
582
filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb))
583
sys.stderr.write("\nscons: warning: %s\n" % e)
584
sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
586
def _scons_internal_warning(e):
587
"""Slightly different from _scons_user_warning in that we use the
588
*current call stack* rather than sys.exc_info() to get our stack trace.
589
This is used by the warnings framework to print warnings."""
590
filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack())
591
sys.stderr.write("\nscons: warning: %s\n" % e.args[0])
592
sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
594
def _scons_internal_error():
595
"""Handle all errors but user errors. Print out a message telling
596
the user what to do in this case and print a normal trace.
598
print 'internal error'
599
traceback.print_exc()
602
def _SConstruct_exists(dirname='', repositories=[], filelist=None):
603
"""This function checks that an SConstruct file exists in a directory.
604
If so, it returns the path of the file. By default, it checks the
608
filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
609
for file in filelist:
610
sfile = os.path.join(dirname, file)
611
if os.path.isfile(sfile):
613
if not os.path.isabs(sfile):
614
for rep in repositories:
615
if os.path.isfile(os.path.join(rep, sfile)):
619
def _set_debug_values(options):
620
global print_memoizer, print_objects, print_stacktrace, print_time
622
debug_values = options.debug
624
if "count" in debug_values:
625
# All of the object counts are within "if __debug__:" blocks,
626
# which get stripped when running optimized (with python -O or
627
# from compiled *.pyo files). Provide a warning if __debug__ is
628
# stripped, so it doesn't just look like --debug=count is broken.
630
if __debug__: enable_count = True
632
count_stats.enable(sys.stdout)
634
msg = "--debug=count is not supported when running SCons\n" + \
635
"\twith the python -O option or optimized (.pyo) modules."
636
SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
637
if "dtree" in debug_values:
638
options.tree_printers.append(TreePrinter(derived=True))
639
options.debug_explain = ("explain" in debug_values)
640
if "findlibs" in debug_values:
641
SCons.Scanner.Prog.print_find_libs = "findlibs"
642
options.debug_includes = ("includes" in debug_values)
643
print_memoizer = ("memoizer" in debug_values)
644
if "memory" in debug_values:
645
memory_stats.enable(sys.stdout)
646
print_objects = ("objects" in debug_values)
647
if "presub" in debug_values:
648
SCons.Action.print_actions_presub = 1
649
if "stacktrace" in debug_values:
651
if "stree" in debug_values:
652
options.tree_printers.append(TreePrinter(status=True))
653
if "time" in debug_values:
655
if "tree" in debug_values:
656
options.tree_printers.append(TreePrinter())
657
if "prepare" in debug_values:
658
SCons.Taskmaster.print_prepare = 1
659
if "duplicate" in debug_values:
660
SCons.Node.FS.print_duplicate = 1
662
def _create_path(plist):
668
path = path + '/' + d
671
def _load_site_scons_dir(topdir, site_dir_name=None):
672
"""Load the site_scons dir under topdir.
673
Prepends site_scons to sys.path, imports site_scons/site_init.py,
674
and prepends site_scons/site_tools to default toolpath."""
676
err_if_not_found = True # user specified: err if missing
678
site_dir_name = "site_scons"
679
err_if_not_found = False
681
site_dir = os.path.join(topdir, site_dir_name)
682
if not os.path.exists(site_dir):
684
raise SCons.Errors.UserError("site dir %s not found."%site_dir)
687
site_init_filename = "site_init.py"
688
site_init_modname = "site_init"
689
site_tools_dirname = "site_tools"
690
# prepend to sys.path
691
sys.path = [os.path.abspath(site_dir)] + sys.path
692
site_init_file = os.path.join(site_dir, site_init_filename)
693
site_tools_dir = os.path.join(site_dir, site_tools_dirname)
694
if os.path.exists(site_init_file):
696
# TODO(2.4): turn this into try:-except:-finally:
699
fp, pathname, description = imp.find_module(site_init_modname,
701
# Load the file into SCons.Script namespace. This is
702
# opaque and clever; m is the module object for the
703
# SCons.Script module, and the exec ... in call executes a
704
# file (or string containing code) in the context of the
705
# module's dictionary, so anything that code defines ends
706
# up adding to that module. This is really short, but all
707
# the error checking makes it longer.
709
m = sys.modules['SCons.Script']
711
fmt = 'cannot import site_init.py: missing SCons.Script module %s'
712
raise SCons.Errors.InternalError(fmt % repr(e))
715
modname = os.path.basename(pathname)[:-len(sfx)]
716
site_m = {"__file__": pathname, "__name__": modname, "__doc__": None}
717
re_special = re.compile("__[^_]+__")
718
for k in m.__dict__.keys():
719
if not re_special.match(k):
720
site_m[k] = m.__dict__[k]
724
except KeyboardInterrupt:
727
fmt = '*** Error loading site_init file %s:\n'
728
sys.stderr.write(fmt % repr(site_init_file))
732
if not re_special.match(k):
733
m.__dict__[k] = site_m[k]
734
except KeyboardInterrupt:
736
except ImportError, e:
737
fmt = '*** cannot import site init file %s:\n'
738
sys.stderr.write(fmt % repr(site_init_file))
743
if os.path.exists(site_tools_dir):
744
# prepend to DefaultToolpath
745
SCons.Tool.DefaultToolpath.insert(0, os.path.abspath(site_tools_dir))
747
def _load_all_site_scons_dirs(topdir, verbose=None):
748
"""Load all of the predefined site_scons dir.
749
Order is significant; we load them in order from most generic
750
(machine-wide) to most specific (topdir).
751
The verbose argument is only for testing.
753
platform = SCons.Platform.platform_default()
756
return os.path.expanduser('~/'+d)
758
if platform == 'win32' or platform == 'cygwin':
759
# Note we use $ here instead of %...% because older
760
# pythons (prior to 2.6?) didn't expand %...% on Windows.
761
# This set of dirs should work on XP, Vista, 7 and later.
763
os.path.expandvars('$ALLUSERSPROFILE\\Application Data\\scons'),
764
os.path.expandvars('$USERPROFILE\\Local Settings\\Application Data\\scons')]
765
appdatadir = os.path.expandvars('$APPDATA\\scons')
766
if appdatadir not in sysdirs:
767
sysdirs.append(appdatadir)
768
sysdirs.append(homedir('.scons'))
770
elif platform == 'darwin': # MacOS X
771
sysdirs=['/Library/Application Support/SCons',
772
'/opt/local/share/scons', # (for MacPorts)
773
'/sw/share/scons', # (for Fink)
774
homedir('Library/Application Support/SCons'),
776
elif platform == 'sunos': # Solaris
777
sysdirs=['/opt/sfw/scons',
780
else: # Linux, HPUX, etc.
781
# assume posix-like, i.e. platform == 'posix'
782
sysdirs=['/usr/share/scons',
785
dirs=sysdirs + [topdir]
787
if verbose: # this is used by unit tests.
788
print "Loading site dir ", d
789
_load_site_scons_dir(d)
791
def test_load_all_site_scons_dirs(d):
792
_load_all_site_scons_dirs(d, True)
794
def version_string(label, module):
795
version = module.__version__
796
build = module.__build__
800
version = version + build
801
fmt = "\t%s: v%s, %s, by %s on %s\n"
805
module.__developer__,
808
def path_string(label, module):
809
path = module.__path__
810
return "\t%s path: %s\n"%(label,path)
814
global this_build_status
816
options = parser.values
818
# Here's where everything really happens.
820
# First order of business: set up default warnings and then
821
# handle the user's warning options, so that we can issue (or
822
# suppress) appropriate warnings about anything that might happen,
823
# as configured by the user.
825
default_warnings = [ SCons.Warnings.WarningOnByDefault,
826
SCons.Warnings.DeprecatedWarning,
829
for warning in default_warnings:
830
SCons.Warnings.enableWarningClass(warning)
831
SCons.Warnings._warningOut = _scons_internal_warning
832
SCons.Warnings.process_warn_strings(options.warn)
834
# Now that we have the warnings configuration set up, we can actually
835
# issue (or suppress) any warnings about warning-worthy things that
836
# occurred while the command-line options were getting parsed.
838
dw = options.delayed_warnings
839
except AttributeError:
842
delayed_warnings.extend(dw)
843
for warning_type, message in delayed_warnings:
844
SCons.Warnings.warn(warning_type, message)
846
if options.diskcheck:
847
SCons.Node.FS.set_diskcheck(options.diskcheck)
849
# Next, we want to create the FS object that represents the outside
850
# world's file system, as that's central to a lot of initialization.
851
# To do this, however, we need to be in the directory from which we
852
# want to start everything, which means first handling any relevant
853
# options that might cause us to chdir somewhere (-C, -D, -U, -u).
854
if options.directory:
855
script_dir = os.path.abspath(_create_path(options.directory))
857
script_dir = os.getcwd()
861
target_top = '.' # directory to prepend to targets
862
while script_dir and not _SConstruct_exists(script_dir,
865
script_dir, last_part = os.path.split(script_dir)
867
target_top = os.path.join(last_part, target_top)
871
if script_dir and script_dir != os.getcwd():
872
if not options.silent:
873
display("scons: Entering directory `%s'" % script_dir)
877
sys.stderr.write("Could not change directory to %s\n" % script_dir)
879
# Now that we're in the top-level SConstruct directory, go ahead
880
# and initialize the FS object that represents the file system,
881
# and make it the build engine default.
882
fs = SCons.Node.FS.get_default_fs()
884
for rep in options.repository:
887
# Now that we have the FS object, the next order of business is to
888
# check for an SConstruct file (or other specified config file).
889
# If there isn't one, we can bail before doing any more work.
892
scripts.extend(options.file)
894
sfile = _SConstruct_exists(repositories=options.repository,
895
filelist=options.file)
897
scripts.append(sfile)
901
# There's no SConstruct, but they specified -h.
902
# Give them the options usage now, before we fail
903
# trying to read a non-existent SConstruct file.
904
raise SConsPrintHelpException
905
raise SCons.Errors.UserError("No SConstruct file found.")
907
if scripts[0] == "-":
910
d = fs.File(scripts[0]).dir
911
fs.set_SConstruct_dir(d)
913
_set_debug_values(options)
914
SCons.Node.implicit_cache = options.implicit_cache
915
SCons.Node.implicit_deps_changed = options.implicit_deps_changed
916
SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
919
SCons.SConf.dryrun = 1
920
SCons.Action.execute_actions = None
922
SCons.SConf.dryrun = 1
924
SCons.SConf.SetBuildType('clean')
926
SCons.SConf.SetBuildType('help')
927
SCons.SConf.SetCacheMode(options.config)
928
SCons.SConf.SetProgressDisplay(progress_display)
930
if options.no_progress or options.silent:
931
progress_display.set_mode(0)
934
_load_site_scons_dir(d.path, options.site_dir)
935
elif not options.no_site_dir:
936
_load_all_site_scons_dirs(d.path)
938
if options.include_dir:
939
sys.path = options.include_dir + sys.path
941
# That should cover (most of) the options. Next, set up the variables
942
# that hold command-line arguments, so the SConscript files that we
943
# read and execute have access to them.
946
for a in parser.largs:
953
SCons.Script._Add_Targets(targets + parser.rargs)
954
SCons.Script._Add_Arguments(xmit_args)
956
# If stdout is not a tty, replace it with a wrapper object to call flush
959
# Tty devices automatically flush after every newline, so the replacement
960
# isn't necessary. Furthermore, if we replace sys.stdout, the readline
961
# module will no longer work. This affects the behavior during
962
# --interactive mode. --interactive should only be used when stdin and
963
# stdout refer to a tty.
964
if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty():
965
sys.stdout = SCons.Util.Unbuffered(sys.stdout)
966
if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty():
967
sys.stderr = SCons.Util.Unbuffered(sys.stderr)
969
memory_stats.append('before reading SConscript files:')
970
count_stats.append(('pre-', 'read'))
972
# And here's where we (finally) read the SConscript files.
974
progress_display("scons: Reading SConscript files ...")
976
start_time = time.time()
978
for script in scripts:
979
SCons.Script._SConscript._SConscript(fs, script)
980
except SCons.Errors.StopError, e:
981
# We had problems reading an SConscript file, such as it
982
# couldn't be copied in to the VariantDir. Since we're just
983
# reading SConscript files and haven't started building
984
# things yet, stop regardless of whether they used -i or -k
986
sys.stderr.write("scons: *** %s Stop.\n" % e)
988
sys.exit(exit_status)
989
global sconscript_time
990
sconscript_time = time.time() - start_time
992
progress_display("scons: done reading SConscript files.")
994
memory_stats.append('after reading SConscript files:')
995
count_stats.append(('post-', 'read'))
997
# Re-{enable,disable} warnings in case they disabled some in
998
# the SConscript file.
1000
# We delay enabling the PythonVersionWarning class until here so that,
1001
# if they explicity disabled it in either in the command line or in
1002
# $SCONSFLAGS, or in the SConscript file, then the search through
1003
# the list of deprecated warning classes will find that disabling
1004
# first and not issue the warning.
1005
#SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
1006
SCons.Warnings.process_warn_strings(options.warn)
1008
# Now that we've read the SConscript files, we can check for the
1009
# warning about deprecated Python versions--delayed until here
1010
# in case they disabled the warning in the SConscript files.
1011
if python_version_deprecated():
1012
msg = "Support for pre-%s Python version (%s) is deprecated.\n" + \
1013
" If this will cause hardship, contact dev@scons.tigris.org."
1014
deprecated_version_string = ".".join(map(str, deprecated_python_version))
1015
SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning,
1016
msg % (deprecated_version_string, python_version_string()))
1018
if not options.help:
1019
SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
1021
# Now re-parse the command-line options (any to the left of a '--'
1022
# argument, that is) with any user-defined command-line options that
1023
# the SConscript files may have added to the parser object. This will
1024
# emit the appropriate error message and exit if any unknown option
1025
# was specified on the command line.
1027
parser.preserve_unknown_options = False
1028
parser.parse_args(parser.largs, options)
1031
help_text = SCons.Script.help_text
1032
if help_text is None:
1033
# They specified -h, but there was no Help() inside the
1034
# SConscript files. Give them the options usage.
1035
raise SConsPrintHelpException
1038
print "Use scons -H for help about command-line options."
1042
# Change directory to the top-level SConstruct directory, then tell
1043
# the Node.FS subsystem that we're all done reading the SConscript
1044
# files and calling Repository() and VariantDir() and changing
1045
# directories and the like, so it can go ahead and start memoizing
1046
# the string values of file system nodes.
1050
SCons.Node.FS.save_strings(1)
1052
# Now that we've read the SConscripts we can set the options
1053
# that are SConscript settable:
1054
SCons.Node.implicit_cache = options.implicit_cache
1055
SCons.Node.FS.set_duplicate(options.duplicate)
1056
fs.set_max_drift(options.max_drift)
1058
SCons.Job.explicit_stack_size = options.stack_size
1060
if options.md5_chunksize:
1061
SCons.Node.FS.File.md5_chunksize = options.md5_chunksize
1063
platform = SCons.Platform.platform_module()
1065
if options.interactive:
1066
SCons.Script.Interactive.interact(fs, OptionsParser, options,
1067
targets, target_top)
1072
nodes = _build_targets(fs, options, targets, target_top)
1076
def _build_targets(fs, options, targets, target_top):
1078
global this_build_status
1079
this_build_status = 0
1081
progress_display.set_mode(not (options.no_progress or options.silent))
1082
display.set_mode(not options.silent)
1083
SCons.Action.print_actions = not options.silent
1084
SCons.Action.execute_actions = not options.no_exec
1085
SCons.Node.FS.do_store_info = not options.no_exec
1086
SCons.SConf.dryrun = options.no_exec
1088
if options.diskcheck:
1089
SCons.Node.FS.set_diskcheck(options.diskcheck)
1091
SCons.CacheDir.cache_enabled = not options.cache_disable
1092
SCons.CacheDir.cache_debug = options.cache_debug
1093
SCons.CacheDir.cache_force = options.cache_force
1094
SCons.CacheDir.cache_show = options.cache_show
1097
CleanTask.execute = CleanTask.show
1099
CleanTask.execute = CleanTask.remove
1102
if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
1103
# They specified targets on the command line or modified
1104
# BUILD_TARGETS in the SConscript file(s), so if they used -u,
1105
# -U or -D, we have to look up targets relative to the top,
1106
# but we build whatever they specified.
1108
lookup_top = fs.Dir(target_top)
1111
targets = SCons.Script.BUILD_TARGETS
1113
# There are no targets specified on the command line,
1114
# so if they used -u, -U or -D, we may have to restrict
1115
# what actually gets built.
1118
if options.climb_up == 1:
1119
# -u, local directory and below
1120
target_top = fs.Dir(target_top)
1121
lookup_top = target_top
1122
elif options.climb_up == 2:
1123
# -D, all Default() targets
1126
elif options.climb_up == 3:
1127
# -U, local SConscript Default() targets
1128
target_top = fs.Dir(target_top)
1129
def check_dir(x, target_top=target_top):
1130
if hasattr(x, 'cwd') and not x.cwd is None:
1131
cwd = x.cwd.srcnode()
1132
return cwd == target_top
1134
# x doesn't have a cwd, so it's either not a target,
1135
# or not a file, so go ahead and keep it as a default
1136
# target and let the engine sort it out:
1138
d = list(filter(check_dir, SCons.Script.DEFAULT_TARGETS))
1139
SCons.Script.DEFAULT_TARGETS[:] = d
1143
targets = SCons.Script._Get_Default_Targets(d, fs)
1146
sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1149
def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
1150
if isinstance(x, SCons.Node.Node):
1154
# Why would ltop be None? Unfortunately this happens.
1155
if ltop is None: ltop = ''
1156
# Curdir becomes important when SCons is called with -u, -C,
1157
# or similar option that changes directory, and so the paths
1158
# of targets given on the command line need to be adjusted.
1159
curdir = os.path.join(os.getcwd(), str(ltop))
1160
for lookup in SCons.Node.arg2nodes_lookups:
1161
node = lookup(x, curdir=curdir)
1162
if node is not None:
1165
node = fs.Entry(x, directory=ltop, create=1)
1166
if ttop and not node.is_under(ttop):
1167
if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node):
1173
nodes = [_f for _f in map(Entry, targets) if _f]
1175
task_class = BuildTask # default action is to build targets
1176
opening_message = "Building targets ..."
1177
closing_message = "done building targets."
1178
if options.keep_going:
1179
failure_message = "done building targets (errors occurred during build)."
1181
failure_message = "building terminated because of errors."
1182
if options.question:
1183
task_class = QuestionTask
1186
task_class = CleanTask
1187
opening_message = "Cleaning targets ..."
1188
closing_message = "done cleaning targets."
1189
if options.keep_going:
1190
failure_message = "done cleaning targets (errors occurred during clean)."
1192
failure_message = "cleaning terminated because of errors."
1193
except AttributeError:
1196
task_class.progress = ProgressObject
1199
def order(dependencies):
1200
"""Randomize the dependencies."""
1202
# This is cribbed from the implementation of
1203
# random.shuffle() in Python 2.X.
1205
for i in range(len(d)-1, 0, -1):
1206
j = int(random.random() * (i+1))
1207
d[i], d[j] = d[j], d[i]
1210
def order(dependencies):
1211
"""Leave the order of dependencies alone."""
1214
if options.taskmastertrace_file == '-':
1215
tmtrace = sys.stdout
1216
elif options.taskmastertrace_file:
1217
tmtrace = open(options.taskmastertrace_file, 'wb')
1220
taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
1222
# Let the BuildTask objects get at the options to respond to the
1223
# various print_* settings, tree_printer list, etc.
1224
BuildTask.options = options
1227
num_jobs = options.num_jobs
1228
jobs = SCons.Job.Jobs(num_jobs, taskmaster)
1231
if jobs.num_jobs == 1:
1232
msg = "parallel builds are unsupported by this version of Python;\n" + \
1233
"\tignoring -j or num_jobs option.\n"
1234
elif sys.platform == 'win32':
1235
msg = fetch_win32_parallel_msg()
1237
SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
1239
memory_stats.append('before building targets:')
1240
count_stats.append(('pre-', 'build'))
1245
closing_message=closing_message,
1246
failure_message=failure_message
1248
if jobs.were_interrupted():
1249
if not options.no_progress and not options.silent:
1250
sys.stderr.write("scons: Build interrupted.\n")
1252
global this_build_status
1254
this_build_status = 2
1256
if this_build_status:
1257
progress_display("scons: " + failure_message)
1259
progress_display("scons: " + closing_message)
1260
if not options.no_exec:
1261
if jobs.were_interrupted():
1262
progress_display("scons: writing .sconsign file.")
1263
SCons.SConsign.write()
1265
progress_display("scons: " + opening_message)
1266
jobs.run(postfunc = jobs_postfunc)
1268
memory_stats.append('after building targets:')
1269
count_stats.append(('post-', 'build'))
1273
def _exec_main(parser, values):
1274
sconsflags = os.environ.get('SCONSFLAGS', '')
1275
all_args = sconsflags.split() + sys.argv[1:]
1277
options, args = parser.parse_args(all_args, values)
1279
if isinstance(options.debug, list) and "pdb" in options.debug:
1281
pdb.Pdb().runcall(_main, parser)
1282
elif options.profile_file:
1283
# compat layer imports "cProfile" for us if it's available.
1284
from profile import Profile
1286
# Some versions of Python 2.4 shipped a profiler that had the
1287
# wrong 'c_exception' entry in its dispatch table. Make sure
1288
# we have the right one. (This may put an unnecessary entry
1289
# in the table in earlier versions of Python, but its presence
1290
# shouldn't hurt anything).
1292
dispatch = Profile.dispatch
1293
except AttributeError:
1296
dispatch['c_exception'] = Profile.trace_dispatch_return
1300
prof.runcall(_main, parser)
1301
except SConsPrintHelpException, e:
1302
prof.dump_stats(options.profile_file)
1306
prof.dump_stats(options.profile_file)
1311
global OptionsParser
1313
global first_command_start
1315
# Check up front for a Python version we do not support. We
1316
# delay the check for deprecated Python versions until later,
1317
# after the SConscript files have been read, in case they
1318
# disable that warning.
1319
if python_version_unsupported():
1320
msg = "scons: *** SCons version %s does not run under Python version %s.\n"
1321
sys.stderr.write(msg % (SCons.__version__, python_version_string()))
1324
parts = ["SCons by Steven Knight et al.:\n"]
1327
parts.append(version_string("script", __main__))
1328
except (ImportError, AttributeError):
1329
# On Windows there is no scons.py, so there is no
1330
# __main__.__version__, hence there is no script version.
1332
parts.append(version_string("engine", SCons))
1333
parts.append(path_string("engine", SCons))
1334
parts.append("Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation")
1335
version = ''.join(parts)
1338
parser = SConsOptions.Parser(version)
1339
values = SConsOptions.SConsValues(parser.get_default_values())
1341
OptionsParser = parser
1344
_exec_main(parser, values)
1345
except SystemExit, s:
1348
except KeyboardInterrupt:
1349
print("scons: Build interrupted.")
1351
except SyntaxError, e:
1352
_scons_syntax_error(e)
1353
except SCons.Errors.InternalError:
1354
_scons_internal_error()
1355
except SCons.Errors.UserError, e:
1356
_scons_user_error(e)
1357
except SConsPrintHelpException:
1360
except SCons.Errors.BuildError, e:
1361
exit_status = e.exitstatus
1363
# An exception here is likely a builtin Python exception Python
1364
# code in an SConscript file. Show them precisely what the
1365
# problem was and where it happened.
1366
SCons.Script._SConscript.SConscript_exception()
1369
memory_stats.print_stats()
1370
count_stats.print_stats()
1373
SCons.Debug.listLoggedInstances('*')
1374
#SCons.Debug.dumpLoggedInstances('*')
1377
SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:")
1379
# Dump any development debug info that may have been enabled.
1380
# These are purely for internal debugging during development, so
1381
# there's no need to control them with --debug= options; they're
1382
# controlled by changing the source code.
1383
SCons.Debug.dump_caller_counts()
1384
SCons.Taskmaster.dump_stats()
1387
total_time = time.time() - SCons.Script.start_time
1389
ct = cumulative_command_time
1391
if last_command_end is None or first_command_start is None:
1394
ct = last_command_end - first_command_start
1395
scons_time = total_time - sconscript_time - ct
1396
print "Total build time: %f seconds"%total_time
1397
print "Total SConscript file execution time: %f seconds"%sconscript_time
1398
print "Total SCons execution time: %f seconds"%scons_time
1399
print "Total command execution time: %f seconds"%ct
1401
sys.exit(exit_status)
1405
# indent-tabs-mode:nil
1407
# vim: set expandtab tabstop=4 shiftwidth=4: