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__"
39 from SCons.Errors import *
42 from SCons.Taskmaster import Taskmaster
45 # Modules and classes that we don't use directly in this script, but
46 # which we want available for use in SConstruct and SConscript files.
48 from SCons.Environment import Environment
49 from SCons.Builder import Builder
56 class BuildTask(SCons.Taskmaster.Task):
57 """An SCons build task."""
62 sys.stderr.write("scons: *** [%s] Error %d\n" % (e.node, e.stat))
65 def set_state(self, state):
66 return self.target.set_state(state)
68 class CleanTask(SCons.Taskmaster.Task):
69 """An SCons clean task."""
71 if hasattr(self.target, "builder"):
72 os.unlink(self.target.path)
73 print "Removed " + self.target.path
75 class ScriptTaskmaster(SCons.Taskmaster.Taskmaster):
76 """Controlling logic for tasks.
78 This is the stock Taskmaster from the build engine, except
79 that we override the up_to_date() method to provide our
80 script-specific up-to-date message for command-line targets,
81 and failed to provide the ignore-errors feature.
83 def up_to_date(self, node, top):
85 print 'scons: "%s" is up to date.' % node
86 SCons.Taskmaster.Taskmaster.up_to_date(self, node)
88 def failed(self, task):
89 if self.ignore_errors:
90 SCons.Taskmaster.Taskmaster.executed(self, task)
92 SCons.Taskmaster.Taskmaster.failed(self, task)
103 task_class = BuildTask # default action is to build targets
108 def _scons_syntax_error(e):
109 """Handle syntax errors. Print out a message and show where the error
112 etype, value, tb = sys.exc_info()
113 lines = traceback.format_exception_only(etype, value)
115 sys.stderr.write(line+'\n')
117 def _scons_user_error(e):
118 """Handle user errors. Print out a message and a description of the
119 error, along with the line number and routine where it occured.
121 etype, value, tb = sys.exc_info()
122 while tb.tb_next is not None:
124 lineno = traceback.tb_lineno(tb)
125 filename = tb.tb_frame.f_code.co_filename
126 routine = tb.tb_frame.f_code.co_name
127 sys.stderr.write("\nSCons error: %s\n" % value)
128 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
130 def _scons_user_warning(e):
131 """Handle user warnings. Print out a message and a description of
132 the warning, along with the line number and routine where it occured.
134 etype, value, tb = sys.exc_info()
135 while tb.tb_next is not None:
137 lineno = traceback.tb_lineno(tb)
138 filename = tb.tb_frame.f_code.co_filename
139 routine = tb.tb_frame.f_code.co_name
140 sys.stderr.write("\nSCons warning: %s\n" % e)
141 sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine))
143 def _scons_other_errors():
144 """Handle all errors but user errors. Print out a message telling
145 the user what to do in this case and print a normal trace.
148 traceback.print_exc()
152 def Conscript(filename):
154 scripts.append(filename)
156 def Default(*targets):
158 for s in string.split(t):
159 default_targets.append(s)
163 if help_option == 'h':
165 print "Use scons -H for help about command-line options."
171 # After options are initialized, the following variables are
174 option_list = [] # list of Option objects
175 short_opts = "" # string of short (single-character) options
176 long_opts = [] # array of long (--) options
177 opt_func = {} # mapping of option strings to functions
180 """Initialize command-line options processing.
182 This is in a subroutine mainly so we can easily single-step over
187 """Class for command-line option information.
189 This exists to provide a central location for everything
190 describing a command-line option, so that we can change
191 options without having to update the code to handle the
192 option in one place, the -h help message in another place,
193 etc. There are no methods here, only attributes.
195 You can initialize an Option with the following:
197 func The function that will be called when this
198 option is processed on the command line.
203 If there is no func, then this Option probably
204 stores an optstring to be printed.
207 The string to be printed in -h output. If no
208 helpline is specified but a help string is
209 specified (the usual case), a helpline will be
210 constructed automatically from the short, long,
211 arg, and help attributes. (In practice, then,
212 setting helpline without setting func allows you
213 to print arbitrary lines of text in the -h
216 short The string for short, single-hyphen
217 command-line options.
218 Do not include the hyphen:
220 'a' for -a, 'xy' for -x and -y, etc.
222 long An array of strings for long, double-hyphen
223 command-line options. Do not include
226 ['my-option', 'verbose']
228 arg If this option takes an argument, this string
229 specifies how you want it to appear in the
230 -h output ('DIRECTORY', 'FILE', etc.).
232 help The help string that will be printed for
233 this option in the -h output. Must be
234 49 characters or fewer.
236 future If non-zero, this indicates that this feature
237 will be supported in a future release, not
238 the currently planned one. SCons will
239 recognize the option, but it won't show up
242 The following attribute is derived from the supplied attributes:
245 A string, with hyphens, describing the flags
246 for this option, as constructed from the
247 specified short, long and arg attributes.
249 All Option objects are stored in the global option_list list,
250 in the order in which they're created. This is the list
251 that's used to generate -h output, so the order in which the
252 objects are created is the order in which they're printed.
254 The upshot is that specifying a command-line option and having
255 everything work correctly is a matter of defining a function to
256 process its command-line argument (set the right flag, update
257 the right value), and then creating an appropriate Option object
258 at the correct point in the code below.
261 def __init__(self, func = None, helpline = None,
262 short = None, long = None, arg = None,
263 help = None, future = None):
274 opts = opts + ['-' + c]
278 l = map(lambda x,a=arg: x + "=" + a, self.long)
279 opts = opts + map(lambda x: '--' + x, l)
280 self.optstring = string.join(opts, ', ')
282 self.helpline = helpline
283 elif help and not future:
284 if len(self.optstring) <= 26:
285 sep = " " * (28 - len(self.optstring))
287 sep = self.helpstring = "\n" + " " * 30
288 self.helpline = " " + self.optstring + sep + self.help
292 option_list.append(self)
294 # Generic routine for to-be-written options, used by multiple
297 def opt_not_yet(opt, arg):
298 sys.stderr.write("Warning: the %s option is not yet implemented\n"
301 # In the following instantiations, the help string should be no
302 # longer than 49 characters. Use the following as a guide:
303 # help = "1234567890123456789012345678901234567890123456789"
305 def opt_ignore(opt, arg):
306 sys.stderr.write("Warning: ignoring %s option\n" % opt)
308 Option(func = opt_ignore,
309 short = 'bmSt', long = ['no-keep-going', 'stop', 'touch'],
310 help = "Ignored for compatibility.")
313 global task_class, current_func
314 task_class = CleanTask
315 current_func = SCons.Taskmaster.current
318 short = 'c', long = ['clean', 'remove'],
319 help = "Remove specified targets and dependencies.")
321 Option(func = opt_not_yet, future = 1,
322 long = ['cache-disable', 'no-cache'],
323 help = "Do not retrieve built targets from Cache.")
325 Option(func = opt_not_yet, future = 1,
326 long = ['cache-force', 'cache-populate'],
327 help = "Copy already-built targets into the Cache.")
329 Option(func = opt_not_yet, future = 1,
330 long = ['cache-show'],
331 help = "Print what would have built Cached targets.")
337 sys.stderr.write("Could not change directory to 'arg'\n")
340 short = 'C', long = ['directory'], arg = 'DIRECTORY',
341 help = "Change to DIRECTORY before doing anything.")
343 Option(func = opt_not_yet,
345 help = "Print file dependency information.")
347 Option(func = opt_not_yet, future = 1,
348 long = ['debug'], arg = 'FLAGS',
349 help = "Print various types of debugging information.")
351 Option(func = opt_not_yet, future = 1,
352 short = 'e', long = ['environment-overrides'],
353 help = "Environment variables override makefiles.")
360 short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE',
361 help = "Read FILE as the top-level SConstruct file.")
363 def opt_help(opt, arg):
367 Option(func = opt_help,
368 short = 'h', long = ['help'],
369 help = "Print defined help message, or this one.")
371 def opt_help_options(opt, arg):
375 Option(func = opt_help_options,
376 short = 'H', long = ['help-options'],
377 help = "Print this message and exit.")
380 ScriptTaskmaster.ignore_errors = 1
383 short = 'i', long = ['ignore-errors'],
384 help = "Ignore errors from build actions.")
388 include_dirs = include_dirs + [arg]
391 short = 'I', long = ['include-dir'], arg = 'DIRECTORY',
392 help = "Search DIRECTORY for imported Python modules.")
407 short = 'j', long = ['jobs'], arg = 'N',
408 help = "Allow N jobs at once.")
410 Option(func = opt_not_yet,
411 short = 'k', long = ['keep-going'],
412 help = "Keep going when a target can't be made.")
414 Option(func = opt_not_yet, future = 1,
415 short = 'l', long = ['load-average', 'max-load'], arg = 'N',
416 help = "Don't start multiple jobs unless load is below N.")
418 Option(func = opt_not_yet, future = 1,
419 long = ['list-derived'],
420 help = "Don't build; list files that would be built.")
422 Option(func = opt_not_yet, future = 1,
423 long = ['list-actions'],
424 help = "Don't build; list files and build actions.")
426 Option(func = opt_not_yet, future = 1,
427 long = ['list-where'],
428 help = "Don't build; list files and where defined.")
431 SCons.Builder.execute_actions = None
434 short = 'n', long = ['no-exec', 'just-print', 'dry-run', 'recon'],
435 help = "Don't build; just print commands.")
437 Option(func = opt_not_yet, future = 1,
438 short = 'o', long = ['old-file', 'assume-old'], arg = 'FILE',
439 help = "Consider FILE to be old; don't rebuild it.")
441 Option(func = opt_not_yet, future = 1,
442 long = ['override'], arg = 'FILE',
443 help = "Override variables as specified in FILE.")
445 Option(func = opt_not_yet, future = 1,
447 help = "Print internal environments/objects.")
449 Option(func = opt_not_yet, future = 1,
450 short = 'q', long = ['question'],
451 help = "Don't build; exit status says if up to date.")
453 Option(func = opt_not_yet, future = 1,
454 short = 'rR', long = ['no-builtin-rules', 'no-builtin-variables'],
455 help = "Clear default environments and variables.")
457 Option(func = opt_not_yet, future = 1,
459 help = "Build dependencies in random order.")
462 SCons.Builder.print_actions = None
465 short = 's', long = ['silent', 'quiet'],
466 help = "Don't print commands.")
468 Option(func = opt_not_yet, future = 1,
469 short = 'u', long = ['up', 'search-up'],
470 help = "Search up directory tree for SConstruct.")
472 def option_v(opt, arg):
473 print "SCons version __VERSION__, by Steven Knight et al."
474 print "Copyright 2001 Steven Knight"
477 Option(func = option_v,
478 short = 'v', long = ['version'],
479 help = "Print the SCons version number and exit.")
481 Option(func = opt_not_yet, future = 1,
482 short = 'w', long = ['print-directory'],
483 help = "Print the current directory.")
485 Option(func = opt_not_yet, future = 1,
486 long = ['no-print-directory'],
487 help = "Turn off -w, even if it was turned on implicitly.")
489 Option(func = opt_not_yet, future = 1,
490 long = ['write-filenames'], arg = 'FILE',
491 help = "Write all filenames examined into FILE.")
493 Option(func = opt_not_yet, future = 1,
494 short = 'W', long = ['what-if', 'new-file', 'assume-new'], arg = 'FILE',
495 help = "Consider FILE to be changed.")
497 Option(func = opt_not_yet, future = 1,
498 long = ['warn-undefined-variables'],
499 help = "Warn when an undefined variable is referenced.")
501 Option(func = opt_not_yet, future = 1,
502 short = 'Y', long = ['repository'], arg = 'REPOSITORY',
503 help = "Search REPOSITORY for source and target files.")
508 for o in option_list:
512 opt_func['-' + c] = o.func
513 short_opts = short_opts + o.short
515 short_opts = short_opts + ":"
519 opt_func['--' + l] = o.func
521 long_opts = long_opts + map(lambda a: a + "=", o.long)
523 long_opts = long_opts + o.long
530 help_opts = filter(lambda x: x.helpline, option_list)
531 s = "Usage: scons [OPTION] [TARGET] ...\n" + "Options:\n" + \
532 string.join(map(lambda x: x.helpline, help_opts), "\n") + "\n"
538 global scripts, help_option, num_jobs, task_class, current_func
542 # It looks like 2.0 changed the name of the exception class
545 getopt_err = getopt.GetoptError
547 getopt_err = getopt.error
550 cmd_opts, t = getopt.getopt(string.split(os.environ['SCONSFLAGS']),
551 short_opts, long_opts)
553 # It's all right if there's no SCONSFLAGS environment variable.
555 except getopt_err, x:
556 _scons_user_warning("SCONSFLAGS " + str(x))
558 for opt, arg in cmd_opts:
559 opt_func[opt](opt, arg)
562 cmd_opts, targets = getopt.getopt(sys.argv[1:], short_opts, long_opts)
563 except getopt_err, x:
566 for opt, arg in cmd_opts:
567 opt_func[opt](opt, arg)
570 for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
571 if os.path.isfile(file):
575 if help_option == 'H':
580 if help_option == 'h':
581 # There's no SConstruct, but they specified either -h or
582 # -H. Give them the options usage now, before we fail
583 # trying to read a non-existent SConstruct file.
587 raise UserError, "No SConstruct file found."
589 # XXX The commented-out code here adds any "scons" subdirs in anything
590 # along sys.path to sys.path. This was an attempt at setting up things
591 # so we can import "node.FS" instead of "SCons.Node.FS". This doesn't
592 # quite fit our testing methodology, though, so save it for now until
593 # the right solutions pops up.
596 #for dir in sys.path:
597 # scons = os.path.join(dir, 'scons')
598 # if os.path.isdir(scons):
599 # dirlist = dirlist + [scons]
600 # dirlist = dirlist + [dir]
604 sys.path = include_dirs + sys.path
607 file, scripts = scripts[0], scripts[1:]
609 exec sys.stdin in globals()
614 sys.stderr.write("Ignoring missing script '%s'\n" % file)
618 if help_option == 'h':
619 # They specified -h, but there was no Help() inside the
620 # SConscript files. Give them the options usage.
625 targets = default_targets
627 nodes = map(lambda x: SCons.Node.FS.default_fs.File(x), targets)
629 calc = SCons.Sig.Calculator(SCons.Sig.MD5)
632 current_func = calc.current
634 taskmaster = ScriptTaskmaster(nodes, task_class, current_func)
636 jobs = SCons.Job.Jobs(num_jobs, taskmaster)
642 if __name__ == "__main__":
647 except KeyboardInterrupt:
648 print "Build interrupted."
649 except SyntaxError, e:
650 _scons_syntax_error(e)
654 _scons_other_errors()