3 # SCons - a Software Constructor
5 # Copyright (c) 2001 Steven Knight
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
19 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
36 # Strip the script directory from sys.path() so on case-insensitive
37 # (WIN32) systems Python doesn't think that the "scons" script is the
39 sys.path = sys.path[1:]
44 from SCons.Errors import *
47 from SCons.Taskmaster import Taskmaster
50 # Modules and classes that we don't use directly in this script, but
51 # which we want available for use in SConstruct and SConscript files.
53 from SCons.Environment import Environment
54 from SCons.Builder import Builder
55 from SCons.Defaults import *
61 class BuildTask(SCons.Taskmaster.Task):
62 """An SCons build task."""
64 if self.target.get_state() == SCons.Node.up_to_date:
66 print 'scons: "%s" is up to date.' % str(self.target)
71 sys.stderr.write("scons: *** [%s] Error %d\n" % (e.node, e.stat))
77 SCons.Taskmaster.Task.executed(self)
78 elif keep_going_on_error:
79 SCons.Taskmaster.Task.fail_continue(self)
81 SCons.Taskmaster.Task.fail_stop(self)
83 class CleanTask(SCons.Taskmaster.Task):
84 """An SCons clean task."""
86 if self.target.builder:
87 os.unlink(self.target.path)
88 print "Removed " + self.target.path
98 task_class = BuildTask # default action is to build targets
102 keep_going_on_error = 0
106 def _scons_syntax_error(e):
107 """Handle syntax errors. Print out a message and show where the error
110 etype, value, tb = sys.exc_info()
111 lines = traceback.format_exception_only(etype, value)
113 sys.stderr.write(line+'\n')
115 def _scons_user_error(e):
116 """Handle user errors. Print out a message and a description of the
117 error, along with the line number and routine where it occured.
119 etype, value, tb = sys.exc_info()
120 while tb.tb_next is not None:
122 lineno = traceback.tb_lineno(tb)
123 filename = tb.tb_frame.f_code.co_filename
124 routine = tb.tb_frame.f_code.co_name
125 sys.stderr.write("\nSCons error: %s\n" % value)
126 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
128 def _scons_user_warning(e):
129 """Handle user warnings. Print out a message and a description of
130 the warning, along with the line number and routine where it occured.
132 etype, value, tb = sys.exc_info()
133 while tb.tb_next is not None:
135 lineno = traceback.tb_lineno(tb)
136 filename = tb.tb_frame.f_code.co_filename
137 routine = tb.tb_frame.f_code.co_name
138 sys.stderr.write("\nSCons warning: %s\n" % e)
139 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
141 def _scons_other_errors():
142 """Handle all errors but user errors. Print out a message telling
143 the user what to do in this case and print a normal trace.
146 traceback.print_exc()
150 def SConscript(filename):
152 scripts.append(SCons.Node.FS.default_fs.File(filename))
154 def Default(*targets):
156 for s in string.split(t):
157 default_targets.append(s)
161 if help_option == 'h':
163 print "Use scons -H for help about command-line options."
169 # After options are initialized, the following variables are
172 option_list = [] # list of Option objects
173 short_opts = "" # string of short (single-character) options
174 long_opts = [] # array of long (--) options
175 opt_func = {} # mapping of option strings to functions
178 """Initialize command-line options processing.
180 This is in a subroutine mainly so we can easily single-step over
185 """Class for command-line option information.
187 This exists to provide a central location for everything
188 describing a command-line option, so that we can change
189 options without having to update the code to handle the
190 option in one place, the -h help message in another place,
191 etc. There are no methods here, only attributes.
193 You can initialize an Option with the following:
195 func The function that will be called when this
196 option is processed on the command line.
201 If there is no func, then this Option probably
202 stores an optstring to be printed.
205 The string to be printed in -h output. If no
206 helpline is specified but a help string is
207 specified (the usual case), a helpline will be
208 constructed automatically from the short, long,
209 arg, and help attributes. (In practice, then,
210 setting helpline without setting func allows you
211 to print arbitrary lines of text in the -h
214 short The string for short, single-hyphen
215 command-line options.
216 Do not include the hyphen:
218 'a' for -a, 'xy' for -x and -y, etc.
220 long An array of strings for long, double-hyphen
221 command-line options. Do not include
224 ['my-option', 'verbose']
226 arg If this option takes an argument, this string
227 specifies how you want it to appear in the
228 -h output ('DIRECTORY', 'FILE', etc.).
230 help The help string that will be printed for
231 this option in the -h output. Must be
232 49 characters or fewer.
234 future If non-zero, this indicates that this feature
235 will be supported in a future release, not
236 the currently planned one. SCons will
237 recognize the option, but it won't show up
240 The following attribute is derived from the supplied attributes:
243 A string, with hyphens, describing the flags
244 for this option, as constructed from the
245 specified short, long and arg attributes.
247 All Option objects are stored in the global option_list list,
248 in the order in which they're created. This is the list
249 that's used to generate -h output, so the order in which the
250 objects are created is the order in which they're printed.
252 The upshot is that specifying a command-line option and having
253 everything work correctly is a matter of defining a function to
254 process its command-line argument (set the right flag, update
255 the right value), and then creating an appropriate Option object
256 at the correct point in the code below.
259 def __init__(self, func = None, helpline = None,
260 short = None, long = None, arg = None,
261 help = None, future = None):
272 opts = opts + ['-' + c]
276 l = map(lambda x,a=arg: x + "=" + a, self.long)
277 opts = opts + map(lambda x: '--' + x, l)
278 self.optstring = string.join(opts, ', ')
280 self.helpline = helpline
281 elif help and not future:
282 if len(self.optstring) <= 26:
283 sep = " " * (28 - len(self.optstring))
285 sep = self.helpstring = "\n" + " " * 30
286 self.helpline = " " + self.optstring + sep + self.help
290 option_list.append(self)
292 # Generic routine for to-be-written options, used by multiple
295 def opt_not_yet(opt, arg):
296 sys.stderr.write("Warning: the %s option is not yet implemented\n"
299 # In the following instantiations, the help string should be no
300 # longer than 49 characters. Use the following as a guide:
301 # help = "1234567890123456789012345678901234567890123456789"
303 def opt_ignore(opt, arg):
304 sys.stderr.write("Warning: ignoring %s option\n" % opt)
306 Option(func = opt_ignore,
307 short = 'bmSt', long = ['no-keep-going', 'stop', 'touch'],
308 help = "Ignored for compatibility.")
311 global task_class, calc
312 task_class = CleanTask
313 class CleanCalculator:
314 def bsig(self, node):
316 def csig(self, node):
318 def current(self, node, sig):
322 calc = CleanCalculator()
325 short = 'c', long = ['clean', 'remove'],
326 help = "Remove specified targets and dependencies.")
328 Option(func = opt_not_yet, future = 1,
329 long = ['cache-disable', 'no-cache'],
330 help = "Do not retrieve built targets from Cache.")
332 Option(func = opt_not_yet, future = 1,
333 long = ['cache-force', 'cache-populate'],
334 help = "Copy already-built targets into the Cache.")
336 Option(func = opt_not_yet, future = 1,
337 long = ['cache-show'],
338 help = "Print what would have built Cached targets.")
344 sys.stderr.write("Could not change directory to 'arg'\n")
347 short = 'C', long = ['directory'], arg = 'DIRECTORY',
348 help = "Change to DIRECTORY before doing anything.")
350 Option(func = opt_not_yet,
352 help = "Print file dependency information.")
354 Option(func = opt_not_yet, future = 1,
355 long = ['debug'], arg = 'FLAGS',
356 help = "Print various types of debugging information.")
358 Option(func = opt_not_yet, future = 1,
359 short = 'e', long = ['environment-overrides'],
360 help = "Environment variables override makefiles.")
367 scripts.append(SCons.Node.FS.default_fs.File(arg))
370 short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE',
371 help = "Read FILE as the top-level SConstruct file.")
373 def opt_help(opt, arg):
377 Option(func = opt_help,
378 short = 'h', long = ['help'],
379 help = "Print defined help message, or this one.")
381 def opt_help_options(opt, arg):
385 Option(func = opt_help_options,
386 short = 'H', long = ['help-options'],
387 help = "Print this message and exit.")
394 short = 'i', long = ['ignore-errors'],
395 help = "Ignore errors from build actions.")
399 include_dirs = include_dirs + [arg]
402 short = 'I', long = ['include-dir'], arg = 'DIRECTORY',
403 help = "Search DIRECTORY for imported Python modules.")
418 short = 'j', long = ['jobs'], arg = 'N',
419 help = "Allow N jobs at once.")
422 global keep_going_on_error
423 keep_going_on_error = 1
426 short = 'k', long = ['keep-going'],
427 help = "Keep going when a target can't be made.")
429 Option(func = opt_not_yet, future = 1,
430 short = 'l', long = ['load-average', 'max-load'], arg = 'N',
431 help = "Don't start multiple jobs unless load is below N.")
433 Option(func = opt_not_yet, future = 1,
434 long = ['list-derived'],
435 help = "Don't build; list files that would be built.")
437 Option(func = opt_not_yet, future = 1,
438 long = ['list-actions'],
439 help = "Don't build; list files and build actions.")
441 Option(func = opt_not_yet, future = 1,
442 long = ['list-where'],
443 help = "Don't build; list files and where defined.")
446 SCons.Builder.execute_actions = None
449 short = 'n', long = ['no-exec', 'just-print', 'dry-run', 'recon'],
450 help = "Don't build; just print commands.")
452 Option(func = opt_not_yet, future = 1,
453 short = 'o', long = ['old-file', 'assume-old'], arg = 'FILE',
454 help = "Consider FILE to be old; don't rebuild it.")
456 Option(func = opt_not_yet, future = 1,
457 long = ['override'], arg = 'FILE',
458 help = "Override variables as specified in FILE.")
460 Option(func = opt_not_yet, future = 1,
462 help = "Print internal environments/objects.")
464 Option(func = opt_not_yet, future = 1,
465 short = 'q', long = ['question'],
466 help = "Don't build; exit status says if up to date.")
468 Option(func = opt_not_yet, future = 1,
469 short = 'rR', long = ['no-builtin-rules', 'no-builtin-variables'],
470 help = "Clear default environments and variables.")
472 Option(func = opt_not_yet, future = 1,
474 help = "Build dependencies in random order.")
477 SCons.Builder.print_actions = None
480 short = 's', long = ['silent', 'quiet'],
481 help = "Don't print commands.")
483 Option(func = opt_not_yet, future = 1,
484 short = 'u', long = ['up', 'search-up'],
485 help = "Search up directory tree for SConstruct.")
487 def option_v(opt, arg):
488 print "SCons version __VERSION__, by Steven Knight et al."
489 print "Copyright 2001 Steven Knight"
492 Option(func = option_v,
493 short = 'v', long = ['version'],
494 help = "Print the SCons version number and exit.")
496 Option(func = opt_not_yet, future = 1,
497 short = 'w', long = ['print-directory'],
498 help = "Print the current directory.")
500 Option(func = opt_not_yet, future = 1,
501 long = ['no-print-directory'],
502 help = "Turn off -w, even if it was turned on implicitly.")
504 Option(func = opt_not_yet, future = 1,
505 long = ['write-filenames'], arg = 'FILE',
506 help = "Write all filenames examined into FILE.")
508 Option(func = opt_not_yet, future = 1,
509 short = 'W', long = ['what-if', 'new-file', 'assume-new'], arg = 'FILE',
510 help = "Consider FILE to be changed.")
512 Option(func = opt_not_yet, future = 1,
513 long = ['warn-undefined-variables'],
514 help = "Warn when an undefined variable is referenced.")
516 Option(func = opt_not_yet, future = 1,
517 short = 'Y', long = ['repository'], arg = 'REPOSITORY',
518 help = "Search REPOSITORY for source and target files.")
523 for o in option_list:
527 opt_func['-' + c] = o.func
528 short_opts = short_opts + o.short
530 short_opts = short_opts + ":"
534 opt_func['--' + l] = o.func
536 long_opts = long_opts + map(lambda a: a + "=", o.long)
538 long_opts = long_opts + o.long
545 help_opts = filter(lambda x: x.helpline, option_list)
546 s = "Usage: scons [OPTION] [TARGET] ...\n" + "Options:\n" + \
547 string.join(map(lambda x: x.helpline, help_opts), "\n") + "\n"
553 global scripts, help_option, num_jobs, task_class, calc
557 # It looks like 2.0 changed the name of the exception class
560 getopt_err = getopt.GetoptError
562 getopt_err = getopt.error
565 cmd_opts, t = getopt.getopt(string.split(os.environ['SCONSFLAGS']),
566 short_opts, long_opts)
568 # It's all right if there's no SCONSFLAGS environment variable.
570 except getopt_err, x:
571 _scons_user_warning("SCONSFLAGS " + str(x))
573 for opt, arg in cmd_opts:
574 opt_func[opt](opt, arg)
577 cmd_opts, targets = getopt.getopt(sys.argv[1:], short_opts, long_opts)
578 except getopt_err, x:
581 for opt, arg in cmd_opts:
582 opt_func[opt](opt, arg)
585 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
586 if os.path.isfile(file):
587 scripts.append(SCons.Node.FS.default_fs.File(file))
590 if help_option == 'H':
595 if help_option == 'h':
596 # There's no SConstruct, but they specified either -h or
597 # -H. Give them the options usage now, before we fail
598 # trying to read a non-existent SConstruct file.
602 raise UserError, "No SConstruct file found."
604 # XXX The commented-out code here adds any "scons" subdirs in anything
605 # along sys.path to sys.path. This was an attempt at setting up things
606 # so we can import "node.FS" instead of "SCons.Node.FS". This doesn't
607 # quite fit our testing methodology, though, so save it for now until
608 # the right solutions pops up.
611 #for dir in sys.path:
612 # scons = os.path.join(dir, 'scons')
613 # if os.path.isdir(scons):
614 # dirlist = dirlist + [scons]
615 # dirlist = dirlist + [dir]
619 sys.path = include_dirs + sys.path
622 f, scripts = scripts[0], scripts[1:]
624 exec sys.stdin in globals()
627 file = open(f.path, "r")
629 sys.stderr.write("Ignoring missing SConscript '%s'\n" % f.path)
631 SCons.Node.FS.default_fs.chdir(f.dir)
632 exec file in globals()
633 SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top)
635 if help_option == 'h':
636 # They specified -h, but there was no Help() inside the
637 # SConscript files. Give them the options usage.
642 targets = default_targets
644 nodes = map(lambda x: SCons.Node.FS.default_fs.Entry(x), targets)
647 calc = SCons.Sig.Calculator(SCons.Sig.MD5)
649 taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, calc)
651 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
657 if __name__ == "__main__":
662 except KeyboardInterrupt:
663 print "Build interrupted."
664 except SyntaxError, e:
665 _scons_syntax_error(e)
669 _scons_other_errors()