Merged revisions 2527-2645 via svnmerge from
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 4 Feb 2008 19:07:24 +0000 (19:07 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 4 Feb 2008 19:07:24 +0000 (19:07 +0000)
http://scons.tigris.org/svn/scons/branches/core

........
  r2528 | stevenknight | 2007-12-13 06:08:21 -0800 (Thu, 13 Dec 2007) | 5 lines

  Remove the .del_binfo() method, no longer needed since the Big Signature
  Refactoring causes us to visit every Node in order during the DAG walk,
  and the BuildInfo object now just holds information for storage in the
  .sconsign file.
........
  r2529 | stevenknight | 2007-12-13 13:17:15 -0800 (Thu, 13 Dec 2007) | 3 lines

  Fix the --keep-going flag so it builds all possible targets even when
  a later top-level target depends on a child that failed its build.
........
  r2530 | stevenknight | 2007-12-14 04:02:05 -0800 (Fri, 14 Dec 2007) | 4 lines

  Issue 1715:  BuildDir(duplicate=0) support for Tex/LaTeX.
  Re-run LaTeX in response to package warnings.
  (Rob Managan)
........
  r2531 | stevenknight | 2007-12-14 07:14:31 -0800 (Fri, 14 Dec 2007) | 3 lines

  Refactor the max_drift logic around fetching stored signatures into
  its own new method.
........
  r2532 | stevenknight | 2007-12-14 07:18:44 -0800 (Fri, 14 Dec 2007) | 3 lines

  Have get_csig() return the stored content signature if max_drift
  says it's okay.
........
  r2533 | stevenknight | 2007-12-14 18:34:51 -0800 (Fri, 14 Dec 2007) | 2 lines

  Issue 1859:  Support SWIG statements like %module(directors="1").
........
  r2534 | stevenknight | 2007-12-15 03:51:13 -0800 (Sat, 15 Dec 2007) | 3 lines

  Python 2.1 portability fix w.r.t. "import SCons" and "import
  SCons.platform.win32" and binding local variables and whatnot.
........
  r2535 | stevenknight | 2007-12-15 03:51:56 -0800 (Sat, 15 Dec 2007) | 2 lines

  Python 1.5 fix:  use the -classic flag when invoking SWIG.
........
  r2536 | stevenknight | 2007-12-15 06:03:48 -0800 (Sat, 15 Dec 2007) | 4 lines

  Support subclasses of the new-style str() class as input to Builders
  and the like.  Also speed up all of the Util.is_*() functions when using
  new-style classes by just using isinstance() internally.
........
  r2537 | stevenknight | 2007-12-15 06:35:49 -0800 (Sat, 15 Dec 2007) | 3 lines

  Issue 1851:  Fix being able to use $PDB and $WINDOWS_INSERT_MANIFEST together.
  (Benoit Belley)
........
  r2538 | stevenknight | 2007-12-15 06:59:43 -0800 (Sat, 15 Dec 2007) | 3 lines

  Handle dangling entries for the Intel C compiler in the Windows registry.
  (Benoit Belley)
........
  r2539 | stevenknight | 2007-12-15 09:51:59 -0800 (Sat, 15 Dec 2007) | 2 lines

  Reorganize library-related tests into a separate subdirectory.
........
  r2540 | stevenknight | 2007-12-15 09:57:29 -0800 (Sat, 15 Dec 2007) | 4 lines

  Issue 1850:  better support for non-standard shared library prefixes and
  suffixes by stripping all prefixes and suffixes in lists of $SHLIBPREFIXES
  and $SHLIBSUFFIXES.  (Benoit Belley)
........
  r2541 | stevenknight | 2007-12-15 18:49:15 -0800 (Sat, 15 Dec 2007) | 2 lines

  Python 1.5 portability fixes.
........
  r2542 | stevenknight | 2007-12-15 19:02:39 -0800 (Sat, 15 Dec 2007) | 3 lines

  Issue 1768:  Have the D language scanner search for .di files as well
  as .d files.  (Jerome Berger)
........
  r2543 | stevenknight | 2007-12-16 14:31:40 -0800 (Sun, 16 Dec 2007) | 3 lines

  Add a find_include_names() method to the Scanner.Classic class to abstract
  out how included names can be generated by subclasses.  (Jerome Berger)
........
  r2544 | stevenknight | 2007-12-16 14:31:54 -0800 (Sun, 16 Dec 2007) | 3 lines

  Add a find_include_names() method to the Scanner.Classic class to abstract
  out how included names can be generated by subclasses.  (Jerome Berger)
........
  r2545 | stevenknight | 2007-12-16 15:04:43 -0800 (Sun, 16 Dec 2007) | 3 lines

  Issue 1860:  Support the D scanner returning multiple modules from a
  single import statement.  (Jerome Berger)
........
  r2546 | stevenknight | 2007-12-16 17:41:17 -0800 (Sun, 16 Dec 2007) | 3 lines

  Issue 1861:  Fix the ability to #include a file (or search other $*PATH
  variables) that has an absoluate path.
........
  r2547 | stevenknight | 2007-12-18 08:09:59 -0800 (Tue, 18 Dec 2007) | 2 lines

  Replace uses of "is_List() or is_Tuple()" with is_Sequence().
........
  r2548 | stevenknight | 2007-12-18 08:13:14 -0800 (Tue, 18 Dec 2007) | 2 lines

  Report the incorrect value in assertions.
........
  r2549 | stevenknight | 2007-12-19 07:58:56 -0800 (Wed, 19 Dec 2007) | 3 lines

  Fix handling #includes of absolute path names when the path doesn't
  exist (implicitly, because it's #ifdef'ed out).
........
  r2550 | stevenknight | 2007-12-19 08:29:24 -0800 (Wed, 19 Dec 2007) | 4 lines

  Fix test path examination when the temporary directory location
  is redirected via symlinks (e.g. /usr/tmp -> /var/tmp on Red Hat).
  (Benoit Belley)
........
  r2551 | stevenknight | 2007-12-19 08:30:17 -0800 (Wed, 19 Dec 2007) | 2 lines

  Fix scons-time path reporting when symlinks are involved.  (Benoit Belley)
........
  r2552 | stevenknight | 2007-12-19 22:51:18 -0800 (Wed, 19 Dec 2007) | 4 lines

  Issue 1855:  Reduce the worker thread stack size to a default of 256
  Kbytes.  Add a --stack-size= command-line option, also configurable
  via SetOption('stack_size').  (Benoit Belley)
........
  r2553 | stevenknight | 2007-12-20 18:25:50 -0800 (Thu, 20 Dec 2007) | 2 lines

  Skip this test if SWIG isn't installed.
........
  r2554 | stevenknight | 2007-12-20 18:26:21 -0800 (Thu, 20 Dec 2007) | 2 lines

  Accomodate slightly different permissions errors on Ubuntu Gutsy.
........
  r2555 | stevenknight | 2007-12-21 02:12:09 -0800 (Fri, 21 Dec 2007) | 3 lines

  Fix a Python 2.2 quirk in the reported file name ("<string>") when
  encountering a SyntaxError in a SConstruct file.
........
  r2556 | stevenknight | 2007-12-21 02:12:35 -0800 (Fri, 21 Dec 2007) | 2 lines

  Enforce order between the build of f1.out and f2.out.
........
  r2557 | stevenknight | 2007-12-21 02:12:55 -0800 (Fri, 21 Dec 2007) | 2 lines

  Don't die if the Python version doesn't have os.path.realpath().
........
  r2558 | stevenknight | 2007-12-21 02:13:19 -0800 (Fri, 21 Dec 2007) | 2 lines

  Refactor the test/build-errors.py script into separate scripts for each test.
........
  r2559 | stevenknight | 2007-12-21 08:08:12 -0800 (Fri, 21 Dec 2007) | 3 lines

  Issue 1864:  Add a CheckDeclaration() call to configure contexts.
  (David Cournapeau)
........
  r2560 | stevenknight | 2007-12-21 08:18:47 -0800 (Fri, 21 Dec 2007) | 2 lines

  Issue 1865:  Improve the CheckTypeSize() code.  (David Cournapeau)
........
  r2561 | stevenknight | 2007-12-21 08:21:47 -0800 (Fri, 21 Dec 2007) | 2 lines

  Fix os.path.realpath() handling (a Python 2.1 portability issue).
........
  r2562 | stevenknight | 2007-12-21 14:08:39 -0800 (Fri, 21 Dec 2007) | 2 lines

  Split CPPDEFINES.py into separate sub-test scripts.
........
  r2563 | stevenknight | 2007-12-21 15:56:26 -0800 (Fri, 21 Dec 2007) | 6 lines

  Support proper expansion of construction variables containing lists
  within expansions like $CPPPATH.
  Change env.subst() to return a list, not a joined string, when the
  input is a list.
  (Konstantin Bozhikov)
........
  r2564 | stevenknight | 2007-12-22 04:15:11 -0800 (Sat, 22 Dec 2007) | 2 lines

  Normalize the ModDate field when comparing generated PDF files.
........
  r2565 | stevenknight | 2007-12-22 22:01:45 -0800 (Sat, 22 Dec 2007) | 5 lines

  Java test refactoring to commonize construction environment initialization
  and searching for javac / javah / jar / rmic.
  Don't look for *_Skel.class files to be created by Java 1.[56].
  Minor Java code changes to deal with compiler warnings.
........
  r2566 | stevenknight | 2007-12-23 05:20:45 -0800 (Sun, 23 Dec 2007) | 2 lines

  Don't still look for *_Skel.class files.
........
  r2567 | stevenknight | 2007-12-23 07:30:36 -0800 (Sun, 23 Dec 2007) | 5 lines

  Fix Intel C compiler issues:
  Issue 1863:  Fix failure to match /opt/intel_cc_* directories.  (Benoit Belley)
  Issue 1866:  Fix topdir when the version isn't specified.  (Jonas Olsson)
  Issue 1867:  Fix use of network licenses.  (Jonas Olsson)
........
  r2573 | stevenknight | 2008-01-01 09:59:16 -0800 (Tue, 01 Jan 2008) | 3 lines

  Add asynchronous subprocess communication via new start() and finish()
  methods.
........
  r2574 | stevenknight | 2008-01-01 10:02:26 -0800 (Tue, 01 Jan 2008) | 4 lines

  Minor code cleanup:  attach the version string to the options parser
  object, instead of passing it in to deal with the lack of nested scopes
  in Python 1.5.2.
........
  r2575 | stevenknight | 2008-01-01 10:08:46 -0800 (Tue, 01 Jan 2008) | 4 lines

  Rename the CacheDir class and let the name CacheDir be a variable that
  can be reset at will, depending on whether CacheDir() support is enabled
  or disabled at any particular time.
........
  r2576 | stevenknight | 2008-01-01 10:14:58 -0800 (Tue, 01 Jan 2008) | 2 lines

  Restore the Node.del_binfo() method and its call in Node.clear().
........
  r2577 | stevenknight | 2008-01-02 07:51:25 -0800 (Wed, 02 Jan 2008) | 6 lines

  Refactor CacheDir support (again) for --interactive mode.  Delay effects
  of --cache-* settings until they're needed by getting rid of the Null()
  object pattern and the functional programming idiom of replacing the
  CacheDebug method.  Have the Environment.CacheDir() method just record
  the path for later instantiation.
........
  r2578 | stevenknight | 2008-01-02 18:48:12 -0800 (Wed, 02 Jan 2008) | 3 lines

  Issue 1657:  Add a --interactive option to create a command-line
  interpreter for re-building targets without re-reading SConscript files.
........
  r2579 | stevenknight | 2008-01-02 21:54:38 -0800 (Wed, 02 Jan 2008) | 2 lines

  Python 1.5.2 portability fix (no use of +=).
........
  r2580 | stevenknight | 2008-01-02 21:54:47 -0800 (Wed, 02 Jan 2008) | 3 lines

  Use a regular expression to avoid having to match a specific
  MD5 checksum value in the --cache-debug output.
........
  r2581 | stevenknight | 2008-01-02 21:54:59 -0800 (Wed, 02 Jan 2008) | 4 lines

  Don't bother looking for shlex.split(), since our compatibility layer
  provides it in older Python version.  Make the compatibility version of
  shlex.split() not treat '.' as a token separator.
........
  r2582 | stevenknight | 2008-01-02 21:56:15 -0800 (Wed, 02 Jan 2008) | 3 lines

  Python 1.5.2 portability fixes:  no list comprehensions, no nested
  scopes, no "for x in" a dictionary.
........
  r2583 | stevenknight | 2008-01-03 07:39:59 -0800 (Thu, 03 Jan 2008) | 3 lines

  Fix a left-over use of a string method.
  Fix printing --interactive help text, which I outright broke last checkin.
........
  r2584 | stevenknight | 2008-01-03 07:58:56 -0800 (Thu, 03 Jan 2008) | 4 lines

  Import the vanilla Python2.5 shlex module, which we'll use as a basis
  for retrofitting to old Python versions to provide shlex.split()
  functionality.
........
  r2585 | stevenknight | 2008-01-03 08:01:02 -0800 (Thu, 03 Jan 2008) | 3 lines

  Modifications to the vanilla Python 2.5 shlex module to make it work
  back to Python 1.5.
........
  r2586 | stevenknight | 2008-01-03 08:04:31 -0800 (Thu, 03 Jan 2008) | 3 lines

  Use the new shlex compatibility module if we're using an old version of
  Python with a native shlex module that has no shlex.split() function.
........
  r2587 | stevenknight | 2008-01-03 09:31:15 -0800 (Thu, 03 Jan 2008) | 3 lines

  Fix the ParseFlags() unit test now that we have a real shlex.split()
  function even on earlier Python versions.
........
  r2588 | stevenknight | 2008-01-06 04:52:05 -0800 (Sun, 06 Jan 2008) | 3 lines

  Add compat/_scons_shlex.py to exception lists for __copyright__ and
  __revision__ strings.
........
  r2589 | stevenknight | 2008-01-06 06:32:07 -0800 (Sun, 06 Jan 2008) | 2 lines

  Remove leftover debug print.
........
  r2590 | stevenknight | 2008-01-06 07:35:46 -0800 (Sun, 06 Jan 2008) | 3 lines

  Change the test to work by wrapping the public .__call__() method
  of the C scanner, instead of the internal .scan() method.
........
  r2591 | stevenknight | 2008-01-06 07:39:12 -0800 (Sun, 06 Jan 2008) | 3 lines

  Use the public CScan.path() method, not the internal CScan.path_function
  attribute.
........
  r2592 | stevenknight | 2008-01-07 02:55:53 -0800 (Mon, 07 Jan 2008) | 2 lines

  Use a tuple instead of a list for the cpp module path(s).
........
  r2593 | stevenknight | 2008-01-07 03:10:28 -0800 (Mon, 07 Jan 2008) | 2 lines

  Don't die if a macro function expands to a non-string (an integer).
........
  r2594 | stevenknight | 2008-01-07 03:29:12 -0800 (Mon, 07 Jan 2008) | 3 lines

  Python 1.5 throws TypeError, not AttributeError if you try to
  string.split() a non-string value.
........
  r2595 | stevenknight | 2008-01-07 03:30:18 -0800 (Mon, 07 Jan 2008) | 3 lines

  Reduce duplicate execution of individual test_*() unit test methods
  by eliminating duplicates (if the set() type is avaiable).
........
  r2596 | stevenknight | 2008-01-07 06:57:30 -0800 (Mon, 07 Jan 2008) | 6 lines

  Add a basic test of in-line #include handling.
  Sort the test names.
  Don't os.path.join() the directory name if we
  find the file in the current directory.
  Use os.curdir instead of hard-coding '.' as the current directory.
........
  r2597 | stevenknight | 2008-01-07 06:59:29 -0800 (Mon, 07 Jan 2008) | 3 lines

  Read files with a new .read_file() method, so it can be overridden
  by subclasses.
........
  r2598 | stevenknight | 2008-01-07 17:59:50 -0800 (Mon, 07 Jan 2008) | 6 lines

  Record the name of the file currently being processed.

  Make the public API (the .__call__() method) passing in a file name to
  be opened, and have it call a new, separate .process_contents() method
  (the old .__call__() method) for handling in-memory strings.
........
  r2599 | stevenknight | 2008-01-07 20:03:18 -0800 (Mon, 07 Jan 2008) | 3 lines

  Make the test failure informative when we don't find the includes
  we expect by printing the expected string and actual output.
........
  r2600 | stevenknight | 2008-01-07 20:24:21 -0800 (Mon, 07 Jan 2008) | 2 lines

  Handle no white space after #include (e.g. #include<foo.h>).
........
  r2601 | stevenknight | 2008-01-07 21:01:27 -0800 (Mon, 07 Jan 2008) | 4 lines

  Fixes for older Python versions:
  No tempfile.mktemp(prefix=) argument.
  No string methods.
........
  r2602 | stevenknight | 2008-01-08 20:57:30 -0800 (Tue, 08 Jan 2008) | 3 lines

  Fix command-line editing of --interactive mode with the readline module
  by only changing sys.stdout to our Unbuffered class if it isn't a tty.
........
  r2603 | stevenknight | 2008-01-08 22:12:20 -0800 (Tue, 08 Jan 2008) | 4 lines

  Fix the --interactive "build" command with no targets:  build the
  specified Default() targets; issue an error message but don't exit if
  Default(None) is explicity specified.
........
  r2604 | stevenknight | 2008-01-09 05:00:36 -0800 (Wed, 09 Jan 2008) | 9 lines

  Improve Python functions used as actions by incorporating into their
  build signatures:
      - literal values referenced by the byte code.
      - values of default arguments
      - code of nested functions
      - values of variables captured by closures
      - names of referenced global variables and functions
  (Benoit Belley)
........
  r2605 | stevenknight | 2008-01-09 06:39:03 -0800 (Wed, 09 Jan 2008) | 4 lines

  Add a Configure.Define() method for adding arbitrary #define lines
  to generated configure header files.
  (David Cournapeau)
........
  r2606 | stevenknight | 2008-01-09 07:33:21 -0800 (Wed, 09 Jan 2008) | 4 lines

  Issue 1858:  Fix the closing message when --clean and --keep-going are
  both used so it only reports errors if some actually occurred.
  (Benoit Belley)
........
  r2607 | stevenknight | 2008-01-09 07:51:55 -0800 (Wed, 09 Jan 2008) | 3 lines

  Issue 1843:  Add a gfortran Tool module for the GNU F95/F2003 compiler.
  (David Cournapeau)
........
  r2608 | stevenknight | 2008-01-09 09:31:15 -0800 (Wed, 09 Jan 2008) | 4 lines

  Issue 1733: If $JARCHDIR isn't set explicitly, use the .java_classdir
  attribute that was set when the Java() Builder built the .class files.
  (Jan Nijtmans)
........
  r2609 | stevenknight | 2008-01-09 11:27:28 -0800 (Wed, 09 Jan 2008) | 4 lines

  Allow Scanner.FindPathDirs objects to not take a dir= keyword argument
  when called.  (The code already detects that and uses the current
  directory if necessary.)
........
  r2610 | stevenknight | 2008-01-09 12:23:26 -0800 (Wed, 09 Jan 2008) | 3 lines

  Allow subclass overrides of results-handling by the addition of
  new initialize_result() and finalize_result() methods.
........
  r2611 | stevenknight | 2008-01-09 14:49:50 -0800 (Wed, 09 Jan 2008) | 6 lines

  Capture new C Scanner glue code that knows how to use $CPPDEFINES to
  evaluate CPP #if/#ifdef/#elif/#else lines.  Currently disabled (including
  the test script that validates the behavior) while we look for the right
  way to let users configure the feature, and work on performance issues
  with its O(N*M) algorithm.
........
  r2612 | stevenknight | 2008-01-24 20:42:57 -0800 (Thu, 24 Jan 2008) | 3 lines

  Fix regular expression comparisons on Windows by escaping the \ path
  separators.
........
  r2613 | stevenknight | 2008-01-24 20:49:04 -0800 (Thu, 24 Jan 2008) | 3 lines

  Rename a created stub script from "cmd.py" so it doesn't mistakenly
  get imported by the "import cmd" statement in Script/Interactive.py.
........
  r2614 | stevenknight | 2008-01-24 20:56:05 -0800 (Thu, 24 Jan 2008) | 4 lines

  Fix a race condition between the actions executed by the worker threads
  by having the dependent action print its own execution line, and telling
  SCons to treat it silently (strfunction=None).
........
  r2615 | stevenknight | 2008-01-24 20:59:03 -0800 (Thu, 24 Jan 2008) | 2 lines

  Remove left-over commented-out lines.
........
  r2616 | stevenknight | 2008-01-24 21:59:49 -0800 (Thu, 24 Jan 2008) | 7 lines

  Windows portability in --interactive mode and its tests:

  Quote target names that may have spaces in them.  Use the .exe suffix
  on a generated executable.  Use the subprocess .wait() method to get the
  subprocess exit status when shelling out on Windows.  Use an Unbuffered
  object for stderr (when it's not a tty).
........
  r2617 | stevenknight | 2008-01-24 22:14:49 -0800 (Thu, 24 Jan 2008) | 3 lines

  Issue 1886:  Fix the ability to build Aliases in --interactive mode.
  (Gary Oberbrunner)
........
  r2618 | stevenknight | 2008-01-24 22:33:29 -0800 (Thu, 24 Jan 2008) | 3 lines

  Issue 1886:  Handle Python versions that throw TypeError when they can't
  pickle a nested function.  (Gary Oberbrunner)
........
  r2619 | stevenknight | 2008-01-24 22:38:44 -0800 (Thu, 24 Jan 2008) | 3 lines

  Fix the LoadableModule.py test when run on Intel Macs (look for the
  string i386 in the file output, in addition to ppc).
........
  r2620 | stevenknight | 2008-01-25 06:50:43 -0800 (Fri, 25 Jan 2008) | 4 lines

  Issue 1892:  use "link" instead of "gnulink" for the Mac tool chain,
  since it doesn't understand the -rpath option and can't use $RPATH.
  (David Cournapeau)
........
  r2621 | stevenknight | 2008-01-25 07:51:56 -0800 (Fri, 25 Jan 2008) | 2 lines

  Issue 1893:  add Intel C compiler support on Mac OS X.  (Benoit Belley)
........
  r2622 | stevenknight | 2008-01-25 21:48:16 -0800 (Fri, 25 Jan 2008) | 2 lines

  Fix how we handle falling back to timestamps when no md5.py module exists.
........
  r2623 | stevenknight | 2008-01-26 16:55:56 -0800 (Sat, 26 Jan 2008) | 5 lines

  Work around a metaclass / new.instancemethod() bug in base Python 2.2 by
  disallowing --debug=memoizer functionality if Python can't handle the
  Memoizer initialization (much like we do for earlier Python versions
  that don't have metaclasses at all).
........
  r2624 | stevenknight | 2008-01-26 18:22:14 -0800 (Sat, 26 Jan 2008) | 4 lines

  Fix CacheDir by simplifying how the NullEnvironment hands back something
  that looks enough like a CacheDir object that the rest of the code
  doesn't require special handling.
........
  r2625 | stevenknight | 2008-01-26 20:56:17 -0800 (Sat, 26 Jan 2008) | 2 lines

  Have the "scons-time time" subcommand handle empty files gracefully.
........
  r2626 | stevenknight | 2008-01-26 20:57:21 -0800 (Sat, 26 Jan 2008) | 3 lines

  Add a Trace() statement to the Node.changed() method if the dependency
  lists are different lengths.
........
  r2627 | stevenknight | 2008-01-26 21:30:59 -0800 (Sat, 26 Jan 2008) | 3 lines

  Have the "scons-time time --which" subcommand handle files that don't
  contain the requested results
........
  r2628 | stevenknight | 2008-01-26 21:52:51 -0800 (Sat, 26 Jan 2008) | 2 lines

  Fix the ability to draw vertical bars with --fmt gnuplot option.
........
  r2629 | stevenknight | 2008-01-26 22:23:10 -0800 (Sat, 26 Jan 2008) | 3 lines

  Allow "scons-time run" to copy non-archive files for timing.
  Document the archive_list config file variable.
........
  r2630 | stevenknight | 2008-01-27 10:38:11 -0800 (Sun, 27 Jan 2008) | 3 lines

  Use the maximum Y value, not the maximum X value, as the top Y endpoint
  of a vertical bar drawn with --fmt=gnuplot.
........
  r2631 | stevenknight | 2008-01-27 12:05:40 -0800 (Sun, 27 Jan 2008) | 2 lines

  Make scons-time more robust when handling log files that have no results.
........
  r2632 | stevenknight | 2008-01-27 12:49:02 -0800 (Sun, 27 Jan 2008) | 2 lines

  Rotate label positions so they don't overwrite each other.
........
  r2633 | stevenknight | 2008-01-27 16:21:17 -0800 (Sun, 27 Jan 2008) | 2 lines

  Extend vertical bars to graph top, not maximum X value.
........
  r2634 | stevenknight | 2008-01-27 18:08:19 -0800 (Sun, 27 Jan 2008) | 2 lines

  Capture three configurations for timing various aspects of SCons.
........
  r2635 | stevenknight | 2008-01-28 04:55:12 -0800 (Mon, 28 Jan 2008) | 2 lines

  Fix jar calls to use "tf" instead of "-t -f" for compatibility with Sun.
........
  r2636 | stevenknight | 2008-01-28 12:49:58 -0800 (Mon, 28 Jan 2008) | 6 lines

  Refactor cut-and-paste tempdir_re() function into a common method
  in QMTest/TestSCons_time.py.
  In the refactored code, fix typo of os.path.relpath() where we meant
  os.path.realpath(), so we follow the /tmp -> /private/tmp symlink on
  Mac OS X.
........
  r2637 | stevenknight | 2008-01-28 15:18:14 -0800 (Mon, 28 Jan 2008) | 5 lines

  Apple portability in the test for explicit "No such file" error messages
  from trying to fork()/exec() a non-existent file name.
  Refactor the tests for (non-)expected output in stderr so they're
  informative if they fail.
........
  r2638 | stevenknight | 2008-01-28 17:54:29 -0800 (Mon, 28 Jan 2008) | 3 lines

  Make the test output deterministic by making the InstallAs() targets
  (file[23].out) depend on the Install() target (file1.out).
........
  r2639 | stevenknight | 2008-01-28 21:37:38 -0800 (Mon, 28 Jan 2008) | 4 lines

  On Mac OS X, add -w to LINKFLAGS to suppress warnings about the
  directories we specify as -L arguments which don't actually exist.
  We just want to make sure that the right directory names show up.
........
  r2640 | stevenknight | 2008-01-28 21:38:36 -0800 (Mon, 28 Jan 2008) | 3 lines

  On Mac OS X, the generated include file for C++ just tacks ".h" on the end
  of the generated .cpp file name.  Define $YACCHXXFILESUFFIX accordingly.
........
  r2641 | stevenknight | 2008-01-29 04:56:18 -0800 (Tue, 29 Jan 2008) | 3 lines

  Add the src/CHANGES.txt for the previous change (Mac OS X bison behavior).
  Add a "bison" application entity to the DocBook infrastructure.
........
  r2642 | stevenknight | 2008-01-30 05:09:02 -0800 (Wed, 30 Jan 2008) | 5 lines

  Improve QT tests for Mac OS X:
  More general regular expression match for a "Generated moc file" warning.
  Copy libmyqt.dylib to the same directory as the "aaa" executable so
  it's found when we run it.
........
  r2643 | stevenknight | 2008-01-30 05:19:23 -0800 (Wed, 30 Jan 2008) | 2 lines

  Skip the test of Java handling SWIG dependencies if swig isn't installed.
........
  r2644 | stevenknight | 2008-01-30 06:44:30 -0800 (Wed, 30 Jan 2008) | 2 lines

  Remove left-over print statement.
........
  r2645 | stevenknight | 2008-01-30 06:52:54 -0800 (Wed, 30 Jan 2008) | 2 lines

  Mac OS X fix:  use .dylib, not .so, in the list of "weird suffixes" we test.
........

git-svn-id: http://scons.tigris.org/svn/scons/trunk@2647 fdb21ef1-2011-0410-befe-b5e4ea1792b1

174 files changed:
QMTest/TestCmd.py
QMTest/TestCommon.py
QMTest/TestSCons.py
QMTest/TestSCons_time.py
doc/man/scons-time.1
doc/man/scons.1
doc/scons.mod
src/CHANGES.txt
src/RELEASE.txt
src/engine/MANIFEST.in
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/CacheDir.py
src/engine/SCons/Conftest.py
src/engine/SCons/Defaults.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Executor.py
src/engine/SCons/Job.py
src/engine/SCons/JobTests.py
src/engine/SCons/Memoize.py
src/engine/SCons/MemoizeTests.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/PathList.py
src/engine/SCons/Platform/posix.py
src/engine/SCons/SConf.py
src/engine/SCons/SConfTests.py
src/engine/SCons/Scanner/C.py
src/engine/SCons/Scanner/D.py
src/engine/SCons/Scanner/LaTeX.py
src/engine/SCons/Scanner/ScannerTests.py
src/engine/SCons/Scanner/__init__.py
src/engine/SCons/Script/Interactive.py [new file with mode: 0644]
src/engine/SCons/Script/Main.py
src/engine/SCons/Script/SConsOptions.py
src/engine/SCons/Subst.py
src/engine/SCons/SubstTests.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/Tool/applelink.py
src/engine/SCons/Tool/gfortran.py [new file with mode: 0644]
src/engine/SCons/Tool/gfortran.xml [new file with mode: 0644]
src/engine/SCons/Tool/intelc.py
src/engine/SCons/Tool/jar.py
src/engine/SCons/Tool/jar.xml
src/engine/SCons/Tool/link.py
src/engine/SCons/Tool/mslink.py
src/engine/SCons/Tool/qt.py
src/engine/SCons/Tool/rmic.py
src/engine/SCons/Tool/swig.py
src/engine/SCons/Tool/tex.py
src/engine/SCons/Tool/yacc.py
src/engine/SCons/Tool/yacc.xml
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
src/engine/SCons/Warnings.py
src/engine/SCons/compat/__init__.py
src/engine/SCons/compat/_scons_shlex.py [new file with mode: 0644]
src/engine/SCons/cpp.py
src/engine/SCons/cppTests.py
src/script/scons-time.py
src/test_strings.py
test/Actions/function.py [new file with mode: 0644]
test/CPPDEFINES/basic.py [new file with mode: 0644]
test/CPPDEFINES/live.py [moved from test/CPPDEFINES.py with 67% similarity]
test/CPPDEFINES/scan.py [new file with mode: 0644]
test/CPPDEFINES/undefined.py [new file with mode: 0644]
test/CPPPATH/absolute-path.py [new file with mode: 0644]
test/CPPPATH/function-expansion.py [new file with mode: 0644]
test/CPPPATH/list-expansion.py [new file with mode: 0644]
test/Configure/Builder-call.py
test/D/DMD.py [moved from test/DMD.py with 100% similarity]
test/D/Scanner.py [new file with mode: 0644]
test/Errors/SyntaxError.py
test/Errors/execute-a-directory.py [new file with mode: 0644]
test/Errors/non-executable-file.py [new file with mode: 0644]
test/Errors/nonexistent-executable.py [new file with mode: 0644]
test/Errors/permission-denied.py [new file with mode: 0644]
test/Install/option--install-sandbox.py
test/Interactive/Alias.py [new file with mode: 0644]
test/Interactive/Default-None.py [new file with mode: 0644]
test/Interactive/Default.py [new file with mode: 0644]
test/Interactive/added-include.py [new file with mode: 0644]
test/Interactive/basic.py [new file with mode: 0644]
test/Interactive/cache-debug.py [new file with mode: 0644]
test/Interactive/cache-disable.py [new file with mode: 0644]
test/Interactive/cache-force.py [new file with mode: 0644]
test/Interactive/cache-show.py [new file with mode: 0644]
test/Interactive/clean.py [new file with mode: 0644]
test/Interactive/exit.py [new file with mode: 0644]
test/Interactive/help.py [new file with mode: 0644]
test/Interactive/implicit-BuildDir.py [new file with mode: 0644]
test/Interactive/option--Q.py [new file with mode: 0644]
test/Interactive/option-i.py [new file with mode: 0644]
test/Interactive/option-j.py [new file with mode: 0644]
test/Interactive/option-k.py [new file with mode: 0644]
test/Interactive/option-n.py [new file with mode: 0644]
test/Interactive/option-s.py [new file with mode: 0644]
test/Interactive/repeat-line.py [new file with mode: 0644]
test/Interactive/shell.py [new file with mode: 0644]
test/Interactive/taskmastertrace.py [new file with mode: 0644]
test/Interactive/tree.py [new file with mode: 0644]
test/Interactive/unknown-command.py [new file with mode: 0644]
test/Interactive/version.py [new file with mode: 0644]
test/Java/JAR.py
test/Java/JARCHDIR.py
test/Java/JARFLAGS.py
test/Java/JAVABOOTCLASSPATH.py
test/Java/JAVACFLAGS.py
test/Java/JAVACLASSPATH.py
test/Java/JAVAH.py
test/Java/JAVASOURCEPATH.py
test/Java/Java-1.4.py
test/Java/Java-1.5.py
test/Java/Java-1.6.py
test/Java/RMIC.py
test/Java/RMICCOM.py
test/Java/RMICCOMSTR.py
test/Java/multi-step.py
test/Java/no-JARCHDIR.py [new file with mode: 0644]
test/Java/source-files.py
test/Java/swig-dependencies.py
test/Libs/LIBPATH.py [moved from test/LIBPATH.py with 100% similarity]
test/Libs/LIBPREFIX.py [moved from test/LIBPREFIX.py with 100% similarity]
test/Libs/LIBPREFIXES.py [moved from test/LIBPREFIXES.py with 100% similarity]
test/Libs/LIBS.py [moved from test/LIBS.py with 100% similarity]
test/Libs/LIBSUFFIX.py [moved from test/LIBSUFFIX.py with 100% similarity]
test/Libs/LIBSUFFIXES.py [moved from test/LIBSUFFIXES.py with 100% similarity]
test/Libs/Library.py [moved from test/Library.py with 100% similarity]
test/Libs/SHLIBPREFIX.py [moved from test/SHLIBPREFIX.py with 100% similarity]
test/Libs/SHLIBSUFFIX.py [moved from test/SHLIBSUFFIX.py with 100% similarity]
test/Libs/SharedLibrary.py [moved from test/SharedLibrary.py with 100% similarity]
test/Libs/SharedLibraryIxes.py [new file with mode: 0644]
test/LoadableModule.py
test/MSVC/pdb-manifest.py [new file with mode: 0644]
test/QT/moc-from-header.py
test/QT/warnings.py
test/Repository/Java.py
test/Repository/JavaH.py
test/Repository/LIBPATH.py
test/Repository/RMIC.py
test/SWIG/module-parens.py [new file with mode: 0644]
test/Scanner/generated.py
test/TEX/build_dir_dup0.py [new file with mode: 0644]
test/TEX/multi-run.py
test/YACC/live.py
test/build-errors.py [deleted file]
test/builderrors.py
test/import.py
test/option-k.py
test/option/debug-includes.py
test/option/debug-memoizer.py
test/option/stack-size.py [new file with mode: 0644]
test/scons-time/func/file.py
test/scons-time/mem/file.py
test/scons-time/obj/file.py
test/scons-time/run/aegis.py
test/scons-time/run/config/archive_list.py
test/scons-time/run/option/quiet.py
test/scons-time/run/option/verbose.py
test/scons-time/run/subversion.py
test/scons-time/time/empty.py [new file with mode: 0644]
test/scons-time/time/file.py
test/scons-time/time/no-result.py [new file with mode: 0644]
test/subclassing.py
test/timestamp-fallback.py
timings/CPPPATH/SConstruct [new file with mode: 0644]
timings/CPPPATH/st.conf [new file with mode: 0644]
timings/JTimer/SConstruct [new file with mode: 0644]
timings/JTimer/st.conf [new file with mode: 0644]
timings/SCons_Bars.py [new file with mode: 0644]
timings/hundred/SConstruct [new file with mode: 0644]
timings/hundred/st.conf [new file with mode: 0644]

index f5e1c71556a9da177746b4de1cc6da9f44e9c2ff..8bf054b37d15e166c231b8b1e0f8ec4567e4efdc 100644 (file)
@@ -181,12 +181,12 @@ version.
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.30.D001 2007/10/01 16:53:55 knight"
-__version__ = "0.30"
+__revision__ = "TestCmd.py 0.31.D001 2008/01/01 09:05:59 knight"
+__version__ = "0.31"
 
+import errno
 import os
 import os.path
-import popen2
 import re
 import shutil
 import stat
@@ -457,6 +457,252 @@ else:
 
     default_sleep_seconds = 1
 
+
+
+try:
+    import subprocess
+except ImportError:
+    # The subprocess module doesn't exist in this version of Python,
+    # so we're going to cobble up something that looks just enough
+    # like its API for our purposes below.
+    import new
+
+    subprocess = new.module('subprocess')
+
+    subprocess.PIPE = 'PIPE'
+    subprocess.STDOUT = 'STDOUT'
+    subprocess.mswindows = (sys.platform == 'win32')
+
+    try:
+        import popen2
+        popen2.Popen3
+    except AttributeError:
+        class Popen3:
+            universal_newlines = 1
+            def __init__(self, command, **kw):
+                if sys.platform == 'win32' and command[0] == '"':
+                    command = '"' + command + '"'
+                (stdin, stdout, stderr) = os.popen3(' ' + command)
+                self.stdin = stdin
+                self.stdout = stdout
+                self.stderr = stderr
+            def close_output(self):
+                self.stdout.close()
+                self.resultcode = self.stderr.close()
+            def wait(self):
+                return self.resultcode
+
+    else:
+        try:
+            popen2.Popen4
+        except AttributeError:
+            # A cribbed Popen4 class, with some retrofitted code from
+            # the Python 1.5 Popen3 class methods to do certain things
+            # by hand.
+            class Popen4(popen2.Popen3):
+                childerr = None
+
+                def __init__(self, cmd, bufsize=-1):
+                    p2cread, p2cwrite = os.pipe()
+                    c2pread, c2pwrite = os.pipe()
+                    self.pid = os.fork()
+                    if self.pid == 0:
+                        # Child
+                        os.dup2(p2cread, 0)
+                        os.dup2(c2pwrite, 1)
+                        os.dup2(c2pwrite, 2)
+                        for i in range(3, popen2.MAXFD):
+                            try:
+                                os.close(i)
+                            except: pass
+                        try:
+                            os.execvp(cmd[0], cmd)
+                        finally:
+                            os._exit(1)
+                        # Shouldn't come here, I guess
+                        os._exit(1)
+                    os.close(p2cread)
+                    self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
+                    os.close(c2pwrite)
+                    self.fromchild = os.fdopen(c2pread, 'r', bufsize)
+                    popen2._active.append(self)
+
+            popen2.Popen4 = Popen4
+
+        class Popen3(popen2.Popen3, popen2.Popen4):
+            universal_newlines = 1
+            def __init__(self, command, **kw):
+                if kw.get('stderr') == 'STDOUT':
+                    apply(popen2.Popen4.__init__, (self, command, 1))
+                else:
+                    apply(popen2.Popen3.__init__, (self, command, 1))
+                self.stdin = self.tochild
+                self.stdout = self.fromchild
+                self.stderr = self.childerr
+
+    subprocess.Popen = Popen3
+
+
+
+# From Josiah Carlson,
+# ASPN : Python Cookbook : Module to allow Asynchronous subprocess use on Windows and Posix platforms
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554
+
+PIPE = subprocess.PIPE
+
+if subprocess.mswindows:
+    from win32file import ReadFile, WriteFile
+    from win32pipe import PeekNamedPipe
+    import msvcrt
+else:
+    import select
+    import fcntl
+
+    try:                    fcntl.F_GETFL
+    except AttributeError:  fcntl.F_GETFL = 3
+
+    try:                    fcntl.F_SETFL
+    except AttributeError:  fcntl.F_SETFL = 4
+
+class Popen(subprocess.Popen):
+    def recv(self, maxsize=None):
+        return self._recv('stdout', maxsize)
+
+    def recv_err(self, maxsize=None):
+        return self._recv('stderr', maxsize)
+
+    def send_recv(self, input='', maxsize=None):
+        return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
+
+    def get_conn_maxsize(self, which, maxsize):
+        if maxsize is None:
+            maxsize = 1024
+        elif maxsize < 1:
+            maxsize = 1
+        return getattr(self, which), maxsize
+
+    def _close(self, which):
+        getattr(self, which).close()
+        setattr(self, which, None)
+
+    if subprocess.mswindows:
+        def send(self, input):
+            if not self.stdin:
+                return None
+
+            try:
+                x = msvcrt.get_osfhandle(self.stdin.fileno())
+                (errCode, written) = WriteFile(x, input)
+            except ValueError:
+                return self._close('stdin')
+            except (subprocess.pywintypes.error, Exception), why:
+                if why[0] in (109, errno.ESHUTDOWN):
+                    return self._close('stdin')
+                raise
+
+            return written
+
+        def _recv(self, which, maxsize):
+            conn, maxsize = self.get_conn_maxsize(which, maxsize)
+            if conn is None:
+                return None
+
+            try:
+                x = msvcrt.get_osfhandle(conn.fileno())
+                (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
+                if maxsize < nAvail:
+                    nAvail = maxsize
+                if nAvail > 0:
+                    (errCode, read) = ReadFile(x, nAvail, None)
+            except ValueError:
+                return self._close(which)
+            except (subprocess.pywintypes.error, Exception), why:
+                if why[0] in (109, errno.ESHUTDOWN):
+                    return self._close(which)
+                raise
+
+            #if self.universal_newlines:
+            #    read = self._translate_newlines(read)
+            return read
+
+    else:
+        def send(self, input):
+            if not self.stdin:
+                return None
+
+            if not select.select([], [self.stdin], [], 0)[1]:
+                return 0
+
+            try:
+                written = os.write(self.stdin.fileno(), input)
+            except OSError, why:
+                if why[0] == errno.EPIPE: #broken pipe
+                    return self._close('stdin')
+                raise
+
+            return written
+
+        def _recv(self, which, maxsize):
+            conn, maxsize = self.get_conn_maxsize(which, maxsize)
+            if conn is None:
+                return None
+
+            try:
+                flags = fcntl.fcntl(conn, fcntl.F_GETFL)
+            except TypeError:
+                flags = None
+            else:
+                if not conn.closed:
+                    fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+
+            try:
+                if not select.select([conn], [], [], 0)[0]:
+                    return ''
+
+                r = conn.read(maxsize)
+                if not r:
+                    return self._close(which)
+
+                #if self.universal_newlines:
+                #    r = self._translate_newlines(r)
+                return r
+            finally:
+                if not conn.closed and not flags is None:
+                    fcntl.fcntl(conn, fcntl.F_SETFL, flags)
+
+disconnect_message = "Other end disconnected!"
+
+def recv_some(p, t=.1, e=1, tr=5, stderr=0):
+    if tr < 1:
+        tr = 1
+    x = time.time()+t
+    y = []
+    r = ''
+    pr = p.recv
+    if stderr:
+        pr = p.recv_err
+    while time.time() < x or r:
+        r = pr()
+        if r is None:
+            if e:
+                raise Exception(disconnect_message)
+            else:
+                break
+        elif r:
+            y.append(r)
+        else:
+            time.sleep(max((x-time.time())/tr, 0))
+    return ''.join(y)
+
+def send_all(p, data):
+    while len(data):
+        sent = p.send(data)
+        if sent is None:
+            raise Exception(disconnect_message)
+        data = buffer(data, sent)
+
+
+
 class TestCmd:
     """Class TestCmd
     """
@@ -703,26 +949,17 @@ class TestCmd:
         dir = self.canonicalize(dir)
         os.rmdir(dir)
 
-    def run(self, program = None,
-                  interpreter = None,
-                  arguments = None,
-                  chdir = None,
-                  stdin = None,
-                  universal_newlines = None):
-        """Runs a test of the program or script for the test
-        environment.  Standard output and error output are saved for
-        future retrieval via the stdout() and stderr() methods.
+    def start(self, program = None,
+                    interpreter = None,
+                    arguments = None,
+                    universal_newlines = None,
+                    **kw):
+        """
+        Starts a program or script for the test environment.
 
         The specified program will have the original directory
-        prepending unless it is enclosed in a [list].
+        prepended unless it is enclosed in a [list].
         """
-        if chdir:
-            oldcwd = os.getcwd()
-            if not os.path.isabs(chdir):
-                chdir = os.path.join(self.workpath(chdir))
-            if self.verbose:
-                sys.stderr.write("chdir(" + chdir + ")\n")
-            os.chdir(chdir)
         if program:
             if type(program) == type('') and not os.path.isabs(program):
                 program = os.path.join(self._cwd, program)
@@ -747,38 +984,56 @@ class TestCmd:
         if universal_newlines is None:
             universal_newlines = self.universal_newlines
 
-        try:
-            import subprocess
-        except ImportError:
-            try:
-                Popen3 = popen2.Popen3
-            except AttributeError:
-                class Popen3:
-                    def __init__(self, command):
-                        (stdin, stdout, stderr) = os.popen3(' ' + command)
-                        self.stdin = stdin
-                        self.stdout = stdout
-                        self.stderr = stderr
-                    def close_output(self):
-                        self.stdout.close()
-                        self.resultcode = self.stderr.close()
-                    def wait(self):
-                        return self.resultcode
-                if sys.platform == 'win32' and cmd_string[0] == '"':
-                    cmd_string = '"' + cmd_string + '"'
-                p = Popen3(cmd_string)
-            else:
-                p = Popen3(cmd, 1)
-                p.stdin = p.tochild
-                p.stdout = p.fromchild
-                p.stderr = p.childerr
+        combine = kw.get('combine', self.combine)
+        if combine:
+            stderr_value = subprocess.STDOUT
         else:
-            p = subprocess.Popen(cmd,
-                                 stdin=subprocess.PIPE,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE,
-                                 universal_newlines=universal_newlines)
+            stderr_value = subprocess.PIPE
+
+        return Popen(cmd,
+                     stdin=subprocess.PIPE,
+                     stdout=subprocess.PIPE,
+                     stderr=stderr_value,
+                     universal_newlines=universal_newlines)
 
+    def finish(self, popen, **kw):
+        """
+        Finishes and waits for the process being run under control of
+        the specified popen argument, recording the exit status,
+        standard output and error output.
+        """
+        popen.stdin.close()
+        self.status = popen.wait()
+        if not self.status:
+            self.status = 0
+        self._stdout.append(popen.stdout.read())
+        if popen.stderr:
+            stderr = popen.stderr.read()
+        else:
+            stderr = ''
+        self._stderr.append(stderr)
+
+    def run(self, program = None,
+                  interpreter = None,
+                  arguments = None,
+                  chdir = None,
+                  stdin = None,
+                  universal_newlines = None):
+        """Runs a test of the program or script for the test
+        environment.  Standard output and error output are saved for
+        future retrieval via the stdout() and stderr() methods.
+
+        The specified program will have the original directory
+        prepended unless it is enclosed in a [list].
+        """
+        if chdir:
+            oldcwd = os.getcwd()
+            if not os.path.isabs(chdir):
+                chdir = os.path.join(self.workpath(chdir))
+            if self.verbose:
+                sys.stderr.write("chdir(" + chdir + ")\n")
+            os.chdir(chdir)
+        p = self.start(program, interpreter, arguments, universal_newlines)
         if stdin:
             if is_List(stdin):
                 for line in stdin:
@@ -788,23 +1043,26 @@ class TestCmd:
         p.stdin.close()
 
         out = p.stdout.read()
-        err = p.stderr.read()
+        if p.stderr is None:
+            err = ''
+        else:
+            err = p.stderr.read()
         try:
-            p.close_output()
+            close_output = p.close_output
         except AttributeError:
             p.stdout.close()
-            p.stderr.close()
+            if not p.stderr is None:
+                p.stderr.close()
+        else:
+            close_output()
+
+        self._stdout.append(out)
+        self._stderr.append(err)
 
         self.status = p.wait()
         if not self.status:
             self.status = 0
 
-        if self.combine:
-            self._stdout.append(out + err)
-        else:
-            self._stdout.append(out)
-            self._stderr.append(err)
-
         if chdir:
             os.chdir(oldcwd)
         if self.verbose >= 2:
@@ -990,18 +1248,24 @@ class TestCmd:
     def readable(self, top, read=1):
         """Make the specified directory tree readable (read == 1)
         or not (read == None).
+
+        This method has no effect on Windows systems, which use a
+        completely different mechanism to control file readability.
         """
 
+        if sys.platform == 'win32':
+            return
+
         if read:
             def do_chmod(fname):
                 try: st = os.stat(fname)
                 except OSError: pass
-                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0400))
+                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|stat.S_IREAD))
         else:
             def do_chmod(fname):
                 try: st = os.stat(fname)
                 except OSError: pass
-                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0400))
+                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~stat.S_IREAD))
 
         if os.path.isfile(top):
             # If it's a file, that's easy, just chmod it.
@@ -1040,16 +1304,29 @@ class TestCmd:
         or not (write == None).
         """
 
-        if write:
-            def do_chmod(fname):
-                try: st = os.stat(fname)
-                except OSError: pass
-                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0200))
+        if sys.platform == 'win32':
+
+            if write:
+                def do_chmod(fname):
+                    try: os.chmod(fname, stat.S_IWRITE)
+                    except OSError: pass
+            else:
+                def do_chmod(fname):
+                    try: os.chmod(fname, stat.S_IREAD)
+                    except OSError: pass
+
         else:
-            def do_chmod(fname):
-                try: st = os.stat(fname)
-                except OSError: pass
-                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0200))
+
+            if write:
+                def do_chmod(fname):
+                    try: st = os.stat(fname)
+                    except OSError: pass
+                    else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0200))
+            else:
+                def do_chmod(fname):
+                    try: st = os.stat(fname)
+                    except OSError: pass
+                    else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0200))
 
         if os.path.isfile(top):
             do_chmod(top)
@@ -1061,18 +1338,24 @@ class TestCmd:
     def executable(self, top, execute=1):
         """Make the specified directory tree executable (execute == 1)
         or not (execute == None).
+
+        This method has no effect on Windows systems, which use a
+        completely different mechanism to control file executability.
         """
 
+        if sys.platform == 'win32':
+            return
+
         if execute:
             def do_chmod(fname):
                 try: st = os.stat(fname)
                 except OSError: pass
-                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0100))
+                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|stat.S_IEXEC))
         else:
             def do_chmod(fname):
                 try: st = os.stat(fname)
                 except OSError: pass
-                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0100))
+                else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~stat.S_IEXEC))
 
         if os.path.isfile(top):
             # If it's a file, that's easy, just chmod it.
index d6b21adf2dee7f6caa4dcbb98df36c24bad16eca..acc63d49266e9afc5818d4b392a5b2cb13697062 100644 (file)
@@ -84,9 +84,10 @@ The TestCommon module also provides the following variables
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 __author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.30.D001 2007/10/01 16:53:55 knight"
-__version__ = "0.30"
+__revision__ = "TestCommon.py 0.31.D001 2008/01/01 09:05:59 knight"
+__version__ = "0.31"
 
+import copy
 import os
 import os.path
 import stat
@@ -378,6 +379,97 @@ class TestCommon(TestCmd):
             print "Writable files: `%s'" % string.join(writable, "', `")
         self.fail_test(missing + writable)
 
+    def _complete(self, actual_stdout, expected_stdout,
+                        actual_stderr, expected_stderr, status, match):
+        """
+        Post-processes running a subcommand, checking for failure
+        status and displaying output appropriately.
+        """
+        if _failed(self, status):
+            expect = ''
+            if status != 0:
+                expect = " (expected %s)" % str(status)
+            print "%s returned %s%s" % (self.program, str(_status(self)), expect)
+            print self.banner('STDOUT ')
+            print actual_stdout
+            print self.banner('STDERR ')
+            print actual_stderr
+            self.fail_test()
+        if not expected_stdout is None and not match(actual_stdout, expected_stdout):
+            self.diff(expected_stdout, actual_stdout, 'STDOUT ')
+            if actual_stderr:
+                print self.banner('STDERR ')
+                print actual_stderr
+            self.fail_test()
+        if not expected_stderr is None and not match(actual_stderr, expected_stderr):
+            print self.banner('STDOUT ')
+            print actual_stdout
+            self.diff(expected_stderr, actual_stderr, 'STDERR ')
+            self.fail_test()
+
+    def start(self, program = None,
+                    interpreter = None,
+                    arguments = None,
+                    universal_newlines = None,
+                    **kw):
+        """
+        Starts a program or script for the test environment.
+
+        This handles the "options" keyword argument and exceptions.
+        """
+        try:
+            options = kw['options']
+            del kw['options']
+        except KeyError:
+            pass
+        else:
+            if options:
+                if arguments is None:
+                    arguments = options
+                else:
+                    arguments = options + " " + arguments
+        try:
+            return apply(TestCmd.start,
+                         (self, program, interpreter, arguments, universal_newlines),
+                         kw)
+        except KeyboardInterrupt:
+            raise
+        except Exception, e:
+            print self.banner('STDOUT ')
+            try:
+                print self.stdout()
+            except IndexError:
+                pass
+            print self.banner('STDERR ')
+            try:
+                print self.stderr()
+            except IndexError:
+                pass
+            raise e
+
+    def finish(self, popen, stdout = None, stderr = '', status = 0, **kw):
+        """
+        Finishes and waits for the process being run under control of
+        the specified popen argument.  Additional arguments are similar
+        to those of the run() method:
+
+                stdout  The expected standard output from
+                        the command.  A value of None means
+                        don't test standard output.
+
+                stderr  The expected error output from
+                        the command.  A value of None means
+                        don't test error output.
+
+                status  The expected exit status from the
+                        command.  A value of None means don't
+                        test exit status.
+        """
+        apply(TestCmd.finish, (self, popen,), kw)
+        match = kw.get('match', self.match)
+        self._complete(self.stdout(), stdout,
+                       self.stderr(), stderr, status, match)
+
     def run(self, options = None, arguments = None,
                   stdout = None, stderr = '', status = 0, **kw):
         """Runs the program under test, checking that the test succeeded.
@@ -415,44 +507,9 @@ class TestCommon(TestCmd):
             del kw['match']
         except KeyError:
             match = self.match
-        try:
-            apply(TestCmd.run, [self], kw)
-        except KeyboardInterrupt:
-            raise
-        except Exception, e:
-            print self.banner('STDOUT ')
-            try:
-                print self.stdout()
-            except IndexError:
-                pass
-            print self.banner('STDERR ')
-            try:
-                print self.stderr()
-            except IndexError:
-                pass
-            raise e
-        if _failed(self, status):
-            expect = ''
-            if status != 0:
-                expect = " (expected %s)" % str(status)
-            print "%s returned %s%s" % (self.program, str(_status(self)), expect)
-            print self.banner('STDOUT ')
-            print self.stdout()
-            print self.banner('STDERR ')
-            print self.stderr()
-            self.fail_test()
-        if not stdout is None and not match(self.stdout(), stdout):
-            self.diff(stdout, self.stdout(), 'STDOUT ')
-            stderr = self.stderr()
-            if stderr:
-                print self.banner('STDERR ')
-                print stderr
-            self.fail_test()
-        if not stderr is None and not match(self.stderr(), stderr):
-            print self.banner('STDOUT ')
-            print self.stdout()
-            self.diff(stderr, self.stderr(), 'STDERR ')
-            self.fail_test()
+        apply(TestCmd.run, [self], kw)
+        self._complete(self.stdout(), stdout,
+                       self.stderr(), stderr, status, match)
 
     def skip_test(self, message="Skipping test.\n"):
         """Skips a test.
index b1fdbc1cbc7a0ce3e309b6713846369a44483d28..6b6f5ed71fd34903347950d0a17e0cac65519a96 100644 (file)
@@ -21,6 +21,7 @@ import os.path
 import re
 import string
 import sys
+import time
 
 import __builtin__
 try:
@@ -138,7 +139,6 @@ def re_escape(str):
     return str
 
 
-
 class TestSCons(TestCommon):
     """Class for testing SCons.
 
@@ -337,8 +337,8 @@ class TestSCons(TestCommon):
         return x
 
     def normalize_pdf(self, s):
-        s = re.sub(r'/CreationDate \(D:[^)]*\)',
-                   r'/CreationDate (D:XXXX)', s)
+        s = re.sub(r'/(Creation|Mod)Date \(D:[^)]*\)',
+                   r'/\1Date (D:XXXX)', s)
         s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
                    r'/ID [<XXXX> <XXXX>]', s)
         s = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
@@ -381,33 +381,114 @@ class TestSCons(TestCommon):
 
         return s
 
-    def java_ENV(self):
+    def java_ENV(self, version=None):
         """
-        Return a default external environment that uses a local Java SDK
-        in preference to whatever's found in the default PATH.
+        Initialize with a default external environment that uses a local
+        Java SDK in preference to whatever's found in the default PATH.
         """
+        try:
+            return self._java_env[version]['ENV']
+        except AttributeError:
+            self._java_env = {}
+        except KeyError:
+            pass
+
         import SCons.Environment
         env = SCons.Environment.Environment()
-        java_path = [
-            '/usr/local/j2sdk1.4.2/bin',
-            '/usr/local/j2sdk1.4.1/bin',
-            '/usr/local/j2sdk1.3.1/bin',
-            '/usr/local/j2sdk1.3.0/bin',
-            '/usr/local/j2sdk1.2.2/bin',
-            '/usr/local/j2sdk1.2/bin',
-            '/usr/local/j2sdk1.1.8/bin',
-            '/usr/local/j2sdk1.1.7/bin',
-            '/usr/local/j2sdk1.1.6/bin',
-            '/usr/local/j2sdk1.1.5/bin',
-            '/usr/local/j2sdk1.1.4/bin',
-            '/usr/local/j2sdk1.1.3/bin',
-            '/usr/local/j2sdk1.1.2/bin',
-            '/usr/local/j2sdk1.1.1/bin',
-            env['ENV']['PATH'],
-        ]
+        self._java_env[version] = env
+
+        def paths(patterns):
+            import glob
+            result = []
+            for p in patterns:
+                paths = glob.glob(p)
+                paths.sort()
+                result.extend(paths)
+            return result
+
+        if version:
+            patterns = [
+                '/usr/lib/jvm/*-%s*/bin' % version,
+                '/usr/local/j2sdk%s*/bin' % version,
+            ]
+            java_path = paths(patterns) + [env['ENV']['PATH']]
+        else:
+            patterns = [
+                '/usr/lib/jvm/*/bin',
+                '/usr/local/j2sdk*/bin',
+            ]
+            java_path = paths(patterns) + [env['ENV']['PATH']]
+
         env['ENV']['PATH'] = string.join(java_path, os.pathsep)
         return env['ENV']
 
+    def java_where_jar(self, version=None):
+        ENV = self.java_ENV(version)
+        if self.detect_tool('jar', ENV=ENV):
+            where_jar = self.detect('JAR', 'jar', ENV=ENV)
+        else:
+            where_jar = self.where_is('jar', ENV['PATH'])
+        if not where_jar:
+            self.skip_test("Could not find Java jar, skipping test(s).\n")
+        return where_jar
+
+    def java_where_java(self, version=None):
+        """
+        Return a path to the java executable.
+        """
+        ENV = self.java_ENV(version)
+        where_java = self.where_is('java', ENV['PATH'])
+        if not where_java:
+            self.skip_test("Could not find Java java, skipping test(s).\n")
+        return where_java
+
+    def java_where_javac(self, version=None):
+        """
+        Return a path to the javac compiler.
+        """
+        ENV = self.java_ENV(version)
+        if self.detect_tool('javac'):
+            where_javac = self.detect('JAVAC', 'javac', ENV=ENV)
+        else:
+            where_javac = self.where_is('javac', ENV['PATH'])
+        if not where_javac:
+            self.skip_test("Could not find Java javac, skipping test(s).\n")
+        self.run(program = where_javac,
+                 arguments = '-version',
+                 stderr=None,
+                 status=None)
+        if version:
+            if string.find(self.stderr(), 'javac %s' % version) == -1:
+                fmt = "Could not find javac for Java version %s, skipping test(s).\n"
+                self.skip_test(fmt % version)
+        else:
+            m = re.search(r'javac (\d\.\d)', self.stderr())
+            if m:
+                version = m.group(1)
+            else:
+                version = None
+        return where_javac, version
+
+    def java_where_javah(self, version=None):
+        ENV = self.java_ENV(version)
+        if self.detect_tool('javah'):
+            where_javah = self.detect('JAVAH', 'javah', ENV=ENV)
+        else:
+            where_javah = self.where_is('javah', ENV['PATH'])
+        if not where_javah:
+            self.skip_test("Could not find Java javah, skipping test(s).\n")
+        return where_javah
+
+    def java_where_rmic(self, version=None):
+        ENV = self.java_ENV(version)
+        if self.detect_tool('rmic'):
+            where_rmic = self.detect('RMIC', 'rmic', ENV=ENV)
+        else:
+            where_rmic = self.where_is('rmic', ENV['PATH'])
+        if not where_rmic:
+            self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
+        return where_rmic
+
     def Qt_dummy_installation(self, dir='qt'):
         # create a dummy qt installation
 
@@ -840,6 +921,22 @@ print "self._msvs_versions =", str(env['MSVS']['VERSIONS'])
         else:
             return distutils.sysconfig.get_python_inc()
 
+    def wait_for(self, fname, timeout=10.0, popen=None):
+        """
+        Waits for the specified file name to exist.
+        """
+        waited = 0.0
+        while not os.path.exists(fname):
+            if timeout and waited >= timeout:
+                sys.stderr.write('timed out waiting for %s to exist\n' % fname)
+                if popen:
+                    popen.stdin.close()
+                    self.status = 1
+                    self.finish(popen)
+                self.fail_test()
+            time.sleep(1.0)
+            waited = waited + 1.0
+
 # In some environments, $AR will generate a warning message to stderr
 # if the library doesn't previously exist and is being created.  One
 # way to fix this is to tell AR to be quiet (sometimes the 'c' flag),
index 102181e4b25b972c80c5acc1b3fc07832ef88848..f3ea49a9c44bfc7f03c1e44e360636cf0296af2e 100644 (file)
@@ -246,6 +246,30 @@ class TestSCons_time(TestCommon):
         self.write(python_name, profile_py % d)
         self.run(program = python_name, interpreter = sys.executable)
 
+    def tempdir_re(self, *args):
+        """
+        Returns a regular expression to match a scons-time
+        temporary directory.
+        """
+        import re
+        import tempfile
+
+        sep = re.escape(os.sep)
+        tempdir = tempfile.gettempdir()
+
+        try:
+            realpath = os.path.realpath
+        except AttributeError:
+            pass
+        else:
+            tempdir = realpath(tempdir)
+
+        args = (tempdir, 'scons-time-',) + args
+        x = apply(os.path.join, args)
+        x = re.escape(x)
+        x = string.replace(x, 'time\\-', 'time\\-[^%s]*' % sep)
+        return x
+
     def write_fake_aegis_py(self, name):
         name = self.workpath(name)
         self.write(name, aegis_py)
index b2de0029b871d19f5ef868ba5c40de8061459213..07832a990cedf41d1647bb8f7d73a54100e1adbd 100644 (file)
@@ -855,6 +855,19 @@ with the
 .B --aegis=
 command-line option.
 .TP
+.B archive_list
+A list of archives (files or directories)
+that will be copied to the temporary directory
+in which SCons will be invoked.
+.BR .tar ,
+.BR .tar.gz ,
+.BR .tgz
+and
+.BR .zip
+files will have their contents unpacked in
+the temporary directory.
+Directory trees and files will be copied as-is.
+.TP
 .B initial_commands
 A list of commands that will be executed
 before the actual timed
index 526a5a3cdb246af08b91029d5002dedf3fe82b27..ae252747750daea8ede20a1d32429561484b9b79 100644 (file)
@@ -867,6 +867,132 @@ This causes cached implicit dependencies to always be used.
 This implies 
 .BR --implicit-cache .
 
+.TP
+--interactive
+Starts SCons in interactive mode.
+The SConscript files are read once and a
+.B "scons>>>"
+prompt is printed.
+Targets may now be rebuilt by typing commands at interactive prompt
+without having to re-read the SConscript files
+and re-initialize the dependency graph from scratch.
+
+SCons interactive mode supports the following commands:
+
+.RS 10
+.TP 6
+.BI build "[OPTIONS] [TARGETS] ..."
+Builds the specified
+.I TARGETS
+(and their dependencies)
+with the specified
+SCons command-line
+.IR OPTIONS .
+.B b
+and
+.B scons
+are synonyms.
+
+The following SCons command-line options affect the
+.B build
+command:
+
+.ES
+--cache-debug=FILE
+--cache-disable, --no-cache
+--cache-force, --cache-populate
+--cache-show
+--debug=TYPE
+-i, --ignore-errors
+-j N, --jobs=N
+-k, --keep-going
+-n, --no-exec, --just-print, --dry-run, --recon
+-Q
+-s, --silent, --quiet
+-s, --silent, --quiet
+--taskmastertrace=FILE
+--tree=OPTIONS
+.EE
+
+.IP "" 6
+Any other SCons command-line options that are specified
+do not cause errors
+but have no effect on the
+.B build
+command
+(mainly because they affect how the SConscript files are read,
+which only happens once at the beginning of interactive mode).
+
+.TP 6
+.BI clean "[OPTIONS] [TARGETS] ..."
+Cleans the specified
+.I TARGETS
+(and their dependencies)
+with the specified options.
+.B c
+is a synonym.
+This command is itself a synonym for
+.B "build --clean"
+
+.TP 6
+.BI exit
+Exits SCons interactive mode.
+You can also exit by terminating input
+(CTRL+D on UNIX or Linux systems,
+CTRL+Z on Windows systems).
+
+.TP 6
+.BI help "[COMMAND]"
+Provides a help message about
+the commands available in SCons interactive mode.
+If
+.I COMMAND
+is specified,
+.B h
+and
+.B ?
+are synonyms.
+
+.TP 6
+.BI shell "[COMMANDLINE]"
+Executes the specified
+.I COMMANDLINE
+in a subshell.
+If no
+.I COMMANDLINE
+is specified,
+executes the interactive command interpreter
+specified in the
+.B SHELL
+environment variable
+(on UNIX and Linux systems)
+or the
+.B COMSPEC
+environment variable
+(on Windows systems).
+.B sh
+and
+.B !
+are synonyms.
+
+.TP 6
+.B version
+Prints SCons version information.
+.RE
+
+An empty line repeats the last typed command.
+Command-line editing can be used if the
+.B readline
+module is available.
+
+.ES
+$ scons --interactive
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons>>> build -n prog
+scons>>> exit
+.EE
+
 .TP
 .RI -j " N" ", --jobs=" N
 Specifies the number of jobs (commands) to run simultaneously.
@@ -1043,6 +1169,26 @@ will get loaded if it exists, and
 .IR dir /site_tools
 will get added to the default toolpath.
 
+.TP
+.RI --stack-size= KILOBYTES
+Set the size stack used to run threads to
+.IR KILOBYTES . 
+This value determines the stack size of the threads used to run jobs.
+These are the threads that execute the actions of the builders for the
+nodes that are out-of-date.
+Note that this option has no effect unless the
+.B num_jobs
+option, which corresponds to -j and --jobs, is larger than one.  Using
+a stack size that is too small may cause stack overflow errors.  This
+usually shows up as segmentation faults that cause scons to abort
+before building anything.  Using a stack size that is too large will
+cause scons to use more memory than required and may slow down the entire
+build process.
+
+The default value is to use a stack size of 256 kilobytes, which should
+be appropriate for most uses.  You should not need to increase this value
+unless you encounter stack overflow errors.
+
 .TP
 -t, --touch
 Ignored for compatibility with GNU
@@ -1244,6 +1390,12 @@ or
 .BR SOURCES .
 These warnings are disabled by default.
 
+.TP
+--warn=stack-size, --warn=no-stack-size
+Enables or disables warnings about requests to set the stack size
+that could not be honored.
+These warnings are enabled by default.
+
 .\" .TP
 .\" .RI --write-filenames= file
 .\" Write all filenames considered into
@@ -1462,6 +1614,7 @@ g++
 g77
 gas
 gcc
+gfortran
 gnulink
 gs
 hpc++
@@ -2917,6 +3070,7 @@ that specify the type of decision function
 to be performed:
 
 .RS 10
+.HP 6
 .B timestamp-newer
 Specifies that a target shall be considered out of date and rebuilt
 if the dependency's timestamp is newer than the target file's timestamp.
@@ -4949,9 +5103,11 @@ which corresponds to --max-drift;
 .B no_exec
 which corresponds to -n, --no-exec, --just-print, --dry-run and --recon;
 .B num_jobs
-which corresponds to -j and --jobs.
+which corresponds to -j and --jobs;
 .B random
-which corresponds to --random.
+which corresponds to --random; and
+.B stack_size
+which corresponds to --stack-size.
 See the documentation for the
 corresponding command line object for information about each specific
 option.
@@ -5066,9 +5222,10 @@ env.SourceCode('no_source.c', None)
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
-.RI env.subst( string ", [" raw ", " target ", " source ", " conv ])
+.RI env.subst( input ", [" raw ", " target ", " source ", " conv ])
 Performs construction variable interpolation
-on the specified string argument.
+on the specified string or sequence argument
+.IR input .
 
 By default,
 leading or trailing white space will
@@ -5100,6 +5257,12 @@ and
 pairs
 (as is done for signature calculation).
 
+If the input is a sequence
+(list or tuple),
+the individual elements of
+the sequence will be expanded,
+and the results will be returned as a list.
+
 The optional
 .I target
 and
@@ -5120,9 +5283,8 @@ calling
 from within a Python function used
 as an SCons action.
 
-By default,
-all returned values are converted
-to their string representation.
+Returned string values or sequence elements
+are converted to their string representation by default.
 The optional
 .I conv
 argument
@@ -6251,6 +6413,82 @@ will return success only if short is two bytes.
 .ES
 .EE
 
+.TP
+.RI Configure.CheckDeclaration( self ", " symbol ", [" includes ", " language ])
+Checks if the specified
+.I symbol
+is declared.
+.I includes
+is a string containing one or more
+.B #include
+lines that will be inserted into the program
+that will be run to test for the existence of the type.
+The optional
+.I language
+argument should be
+.B C
+or
+.B C++
+and selects the compiler to be used for the check;
+the default is "C".
+
+.TP
+.RI Configure.Define(self ", " symbol ", [" value ", " comment ])
+This function does not check for anything, but defines a
+preprocessor symbol that will be added to the configuration header file.
+It is the equivalent of AC_DEFINE,
+and defines the symbol
+.I name
+with the optional
+.B value
+and the optional comment
+.BR comment .
+
+.IP
+Examples:
+
+.ES
+env = Environment()
+conf = Configure( env )
+
+# Puts the following line in the config header file:
+#    #define A_SYMBOL
+conf.Define('A_SYMBOL')
+
+# Puts the following line in the config header file:
+#    #define A_SYMBOL 1
+conf.Define('A_SYMBOL', 1)
+.EE
+
+.IP
+Be careful about quoting string values, though:
+
+.ES
+env = Environment()
+conf = Configure( env )
+
+# Puts the following line in the config header file:
+#    #define A_SYMBOL YA
+conf.Define('A_SYMBOL', "YA")
+
+# Puts the following line in the config header file:
+#    #define A_SYMBOL "YA"
+conf.Define('A_SYMBOL', '"YA"')
+.EE
+
+.IP
+For comment:
+
+.ES
+env = Environment()
+conf = Configure( env )
+
+# Puts the following lines in the config header file:
+#    /* Set to 1 if you have a symbol */
+#    #define A_SYMBOL 1
+conf.Define('A_SYMBOL', 1, 'Set to 1 if you have a symbol')
+.EE
+
 .EE
 You can define your own custom checks. 
 in addition to the predefined checks.
index c23e6ae04029687433d1179e02ab17642ff15f27..e590368df7c0a7d4d447adc83703657ca432c26c 100644 (file)
@@ -22,6 +22,7 @@
 <!ENTITY as             "<application>as</application>">
 <!ENTITY Autoconf       "<application>Autoconf</application>">
 <!ENTITY Automake       "<application>Automake</application>">
+<!ENTITY bison          "<application>bison</application>">
 <!ENTITY cc             "<application>cc</application>">
 <!ENTITY Cons           "<application>Cons</application>">
 <!ENTITY cp             "<application>cp</application>">
index c8dc7d3f733b9df94ee8174d2c337d966f043b44..81b54e65c3702cf2e7dfef969e841d8b78c3ff11 100644 (file)
@@ -8,6 +8,141 @@
 
 
 
+RELEASE 0.XX - XXX
+
+  From Benoit Belley:
+
+  - Fix the --keep-going flag so it builds all possible targets even when
+    a later top-level target depends on a child that failed its build.
+
+  - Fix being able to use $PDB and $WINDWOWS_INSERT_MANIFEST together.
+
+  - Don't crash if un-installing the Intel C compiler leaves left-over,
+    dangling entries in the Windows registry.
+
+  - Improve support for non-standard library prefixes and suffixes by
+    stripping all prefixes/suffixes from file name string as appropriate.
+
+  - Reduce the default stack size for -j worker threads to 256 Kbytes.
+    Provide user control over this value by adding --stack-size and
+    --warn=stack-size options, and a SetOption('stack_size') function.
+
+  - Fix a crash on Linux systems when trying to use the Intel C compiler
+    and no /opt/intel_cc_* directories are found.
+
+  - Improve using Python functions as actions by incorporating into
+    a FunctionAction's signature:
+      - literal values referenced by the byte code.
+      - values of default arguments
+      - code of nested functions
+      - values of variables captured by closures
+      - names of referenced global variables and functions
+
+  - Fix the closing message when --clean and --keep-going are both
+    used and no errors occur.
+
+  - Add support for the Intel C compiler on Mac OS X.
+
+  From Jérôme Berger:
+
+  - Have the D language scanner search for .di files as well as .d files.
+
+  - Add a find_include_names() method to the Scanner.Classic class to
+    abstract out how included names can be generated by subclasses.
+
+  - Allow the D language scanner to detect multiple modules imported by
+    a single statement.
+
+  From Konstantin Bozhikov:
+
+  - Support expansion of construction variables that contain or refer
+    to lists of other variables or Nodes within expansions like $PCPPATH.
+
+  - Change variable substitution (the env.subst() method) so that an
+    input sequence (list or tuple) is preserved as a list in the output.
+
+  From David Cournapeau:
+
+  - Add a CheckDeclaration() call to configure contexts.
+
+  - Improve the CheckTypeSize() code.
+
+  - Add a Define() call to configure contexts, to add arbitrary #define
+    lines to a generated configure header file.
+
+  - Add a "gfortran" Tool module for the GNU F95/F2003 compiler.
+
+  - Avoid use of -rpath with the Mac OS X linker.
+
+  From Steven Knight:
+
+  - Support the ability to subclass the new-style "str" class as input
+    to Builders.
+
+  - Improve the performance of our type-checking by using isinstance()
+    with new-style classes.
+
+  - Fix #include (and other $*PATH variables searches) of files with
+    absolute path names.  Don't die if they don't exist (due to being
+    #ifdef'ed out or the like).
+
+  - Fix --interactive mode when Default(None) is used.
+
+  - Fix --debug=memoizer to work around a bug in base Python 2.2 metaclass
+    initialization (by just not allowing Memoization in Python versions
+    that have the bug).
+
+  - Have the "scons-time time" subcommand handle empty log files, and
+    log files that contain no results specified by the --which option.
+
+  - Fix the max Y of vertical bars drawn by "scons-time --fmt=gnuplot".
+
+  - On Mac OS X, account for the fact that the header file generated
+    from a C++ file will be named (e.g.) file.cpp.h, not file.hpp.
+
+  From Rob Managan:
+
+  - Enhance TeX and LaTeX support to work with BuildDir(duplicate=0).
+
+  - Re-run LaTeX when it issues a package warning that it must be re-run.
+
+  From Jan Nijtmans:
+
+  - If $JARCHDIR isn't set explicitly, use the .java_classdir attribute
+    that was set when the Java() Builder built the .class files.
+
+  From Gary Oberbrunner:
+
+  - Fix the ability to build an Alias in --interactive mode.
+
+  - Fix the ability to hash the contents of actions for nested Python
+    functions on Python versions where the inability to pickle them
+    returns a TypeError (instead of the documented PicklingError).
+
+  From Jonas Olsson:
+
+  - Fix use of the Intel C compiler when the top compiler directory,
+    but not the compiler version, is specified.
+
+  - Handle Intel C compiler network license files (port@system).
+
+  From Adam Simpkins:
+
+  - Add a --interactive option that starts a session for building (or
+    cleaning) targets without re-reading the SConscript files every time.
+
+  - Fix use of readline command-line editing in --interactive mode.
+
+  - Have the --interactive mode "build" command with no arguments
+    build the specified Default() targets.
+
+  From Ben Webb:
+
+  - Support the SWIG %module statement with following modifiers in
+    parenthese (e.g., '%module(directors="1")').
+
+
+
 RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
 
   From Benoit Belley:
index ca01607bffdcd570d9afa4aea8ff162d94972e9c..7327f21bed4f7034b4c289ae1841e0a70ec6e81a 100644 (file)
@@ -25,6 +25,43 @@ RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
   This is the eighth beta release of SCons.  Please consult the
   CHANGES.txt file for a list of specific changes since last release.
 
+  Please note the following important changes since release 0.97.0d20071212:
+
+    --  THE env.subst() METHOD NOW RETURNS A LIST WHEN THE INPUT IS A SEQUENCE
+
+        The env.subst() method now returns a list with the elements
+        expanded when given a list as input.  Previously, the env.subst()
+        method would always turn its result into a string.
+
+        This behavior was changed because way it interfered with
+        being able to include things like lists within the expansion
+        of variables like $CPPPATH and have SCons understand that the
+        elements of the "internal" lists still needed to be treated
+        separately.  This would show up as a list like ['subdir1',
+        'subdir'] showing up in a command line as "-Isubdir1 subdir".
+
+    --  THE Jar() BUILDER NOW USES THE Java() BUILDER CLASSDIR BY DEFAULT
+
+        By default, the Jar() Builder will now use the class directory
+        specified when the Java() builder is called.  So the following
+        input:
+
+            classes = env.Java('classes', 'src')
+            env.Jar('out.jar', classes)
+
+        Will cause "-C classes" to be passed the "jar" command invocation,
+        and the Java classes in the "out.jar" file will not be prefixed
+        "classes/".
+
+        Explicitly setting the $JARCHDIR variable overrides this default
+        behavior.  The old behavior of not passing any -C option to the
+        "jar" command can be preserved by explicitly setting $JARCHDIR
+        to None:
+
+            env = Environment(JARCHDIR = None)
+
+        The above setting is compatible with older versions of SCons.
+
   Please note the following important changes since release 0.97.0d20070918:
 
     --  SCons REDEFINES PYTHON open() AND file() ON Windows TO NOT PASS
index 52323d20c11c0b5a14f44ccbe11fa7851f96477e..093fbd95ecc125224f7bff7f5c09ec7bce1f56c4 100644 (file)
@@ -6,6 +6,7 @@ SCons/compat/_scons_hashlib.py
 SCons/compat/_scons_optparse.py
 SCons/compat/_scons_sets.py
 SCons/compat/_scons_sets15.py
+SCons/compat/_scons_shlex.py
 SCons/compat/_scons_subprocess.py
 SCons/compat/_scons_textwrap.py
 SCons/compat/_scons_UserString.py
@@ -54,6 +55,7 @@ SCons/Scanner/Prog.py
 SCons/SConf.py
 SCons/SConsign.py
 SCons/Script/__init__.py
+SCons/Script/Interactive.py
 SCons/Script/Main.py
 SCons/Script/SConscript.py
 SCons/Script/SConsOptions.py
@@ -89,6 +91,7 @@ SCons/Tool/g++.py
 SCons/Tool/g77.py
 SCons/Tool/gas.py
 SCons/Tool/gcc.py
+SCons/Tool/gfortran.py
 SCons/Tool/gnulink.py
 SCons/Tool/gs.py
 SCons/Tool/hpc++.py
index c2c11581ab376c18116172a04d4706e6b6231825..cd4bf6aa0bf29781f6240fba99c7ece8599592fd 100644 (file)
@@ -97,6 +97,7 @@ way for wrapping up the functions.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import cPickle
 import dis
 import os
 import os.path
@@ -150,6 +151,138 @@ else:
                 i = i+1
         return string.join(result, '')
 
+
+def _callable_contents(obj):
+    """Return the signature contents of a callable Python object.
+    """
+    try:
+        # Test if obj is a method.
+        return _function_contents(obj.im_func)
+
+    except AttributeError:
+        try:
+            # Test if obj is a callable object.
+            return _function_contents(obj.__call__.im_func)
+
+        except AttributeError:
+            try:
+                # Test if obj is a code object.
+                return _code_contents(obj)
+
+            except AttributeError:
+                    # Test if obj is a function object.
+                    return _function_contents(obj)
+
+
+def _object_contents(obj):
+    """Return the signature contents of any Python object.
+    
+    We have to handle the case where object contains a code object
+    since it can be pickled directly.
+    """
+    try:
+        # Test if obj is a method.
+        return _function_contents(obj.im_func)
+
+    except AttributeError:
+        try:
+            # Test if obj is a callable object.
+            return _function_contents(obj.__call__.im_func)
+
+        except AttributeError:
+            try:
+                # Test if obj is a code object.
+                return _code_contents(obj)
+
+            except AttributeError:
+                try:
+                    # Test if obj is a function object.
+                    return _function_contents(obj)
+
+                except AttributeError:
+                    # Should be a pickable Python object. 
+                    try:
+                        return cPickle.dumps(obj)
+                    except (cPickle.PicklingError, TypeError):
+                        # This is weird, but it seems that nested classes
+                        # are unpickable. The Python docs say it should
+                        # always be a PicklingError, but some Python
+                        # versions seem to return TypeError.  Just do
+                        # the best we can.
+                        return str(obj)
+
+
+def _code_contents(code):
+    """Return the signature contents of a code object.
+
+    By providing direct access to the code object of the
+    function, Python makes this extremely easy.  Hooray!
+    
+    Unfortunately, older versions of Python include line
+    number indications in the compiled byte code.  Boo!
+    So we remove the line number byte codes to prevent
+    recompilations from moving a Python function.
+    """
+
+    contents = []
+
+    # The code contents depends on the number of local variables
+    # but not their actual names.
+    contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames)))
+    try:
+        contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)))
+    except AttributeError:
+        # Older versions of Python do not support closures.
+        contents.append(",0,0")
+
+    # The code contents depends on any constants accessed by the
+    # function. Note that we have to call _object_contents on each
+    # constants because the code object of nested functions can
+    # show-up among the constants. 
+    # 
+    # Note that we also always ignore the first entry of co_consts
+    # which contains the function doc string. We assume that the
+    # function does not access its doc string.
+    contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')')
+                                 
+    # The code contents depends on the variable names used to
+    # accessed global variable, as changing the variable name changes
+    # the variable actually accessed and therefore changes the
+    # function result.
+    contents.append(',(' + string.join(map(_object_contents,code.co_names),',') + ')')
+
+
+    # The code contents depends on its actual code!!!
+    contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')')
+
+    return string.join(contents, '')
+
+
+def _function_contents(func):
+    """Return the signature contents of a function."""
+
+    contents = [_code_contents(func.func_code)]
+
+    # The function contents depends on the value of defaults arguments
+    if func.func_defaults:
+        contents.append(',(' + string.join(map(_object_contents,func.func_defaults),',') + ')')
+    else:
+        contents.append(',()')
+
+    # The function contents depends on the closure captured cell values.
+    try:
+        closure = func.func_closure or []
+    except AttributeError:
+        # Older versions of Python do not support closures.
+        closure = []
+
+    #xxx = [_object_contents(x.cell_contents) for x in closure]
+    xxx = map(lambda x: _object_contents(x.cell_contents), closure)
+    contents.append(',(' + string.join(xxx, ',') + ')')
+
+    return string.join(contents, '')
+        
+
 def _actionAppend(act1, act2):
     # This function knows how to slap two actions together.
     # Mainly, it handles ListActions by concatenating into
@@ -643,6 +776,16 @@ class FunctionAction(_ActionAction):
                     'accepts (target, source, env) as parameters.')
 
         self.execfunction = execfunction
+        try:
+            self.funccontents = _callable_contents(execfunction)
+        except AttributeError:
+            try:
+                # See if execfunction will do the heavy lifting for us.
+                self.gc = execfunction.get_contents
+            except AttributeError:
+                # This is weird, just do the best we can.
+                self.funccontents = _object_contents(execfunction)
+
         apply(_ActionAction.__init__, (self,)+args, kw)
         self.varlist = kw.get('varlist', [])
         self.cmdstr = cmdstr
@@ -716,46 +859,14 @@ class FunctionAction(_ActionAction):
         return result
 
     def get_contents(self, target, source, env):
-        """Return the signature contents of this callable action.
-
-        By providing direct access to the code object of the
-        function, Python makes this extremely easy.  Hooray!
-
-        Unfortunately, older versions of Python include line
-        number indications in the compiled byte code.  Boo!
-        So we remove the line number byte codes to prevent
-        recompilations from moving a Python function.
-        """
-        execfunction = self.execfunction
+        """Return the signature contents of this callable action."""
         try:
-            # Test if execfunction is a function.
-            code = execfunction.func_code.co_code
+            contents = self.gc(target, source, env)
         except AttributeError:
-            try:
-                # Test if execfunction is a method.
-                code = execfunction.im_func.func_code.co_code
-            except AttributeError:
-                try:
-                    # Test if execfunction is a callable object.
-                    code = execfunction.__call__.im_func.func_code.co_code
-                except AttributeError:
-                    try:
-                        # See if execfunction will do the heavy lifting for us.
-                        gc = self.execfunction.get_contents
-                    except AttributeError:
-                        # This is weird, just do the best we can.
-                        contents = str(self.execfunction)
-                    else:
-                        contents = gc(target, source, env)
-                else:
-                    contents = str(code)
-            else:
-                contents = str(code)
-        else:
-            contents = str(code)
-        contents = remove_set_lineno_codes(contents)
+            contents = self.funccontents
+
         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
-                                                     self.varlist)))
+                                                    self.varlist)))
 
     def get_implicit_deps(self, target, source, env):
         return []
index 06030e3bb4a9575b9b9a6a2e8d4b437108a8331f..2ad4bef7614665e3153289f8affb270727dc0598 100644 (file)
@@ -1490,25 +1490,30 @@ class FunctionActionTestCase(unittest.TestCase):
         def LocalFunc():
             pass
 
-        matches = [
-            "d\000\000S",
-            "d\x00\x00S",
+        func_matches = [
+            "0,0,0,0,(),(),(d\000\000S),(),()",
+            "0,0,0,0,(),(),(d\x00\x00S),(),()",
+            ]
+        
+        meth_matches = [
+            "1,1,0,0,(),(),(d\000\000S),(),()",
+            "1,1,0,0,(),(),(d\x00\x00S),(),()",
         ]
 
         a = SCons.Action.FunctionAction(GlobalFunc)
         c = a.get_contents(target=[], source=[], env=Environment())
-        assert c in matches, repr(c)
+        assert c in func_matches, repr(c)
 
         a = SCons.Action.FunctionAction(LocalFunc)
         c = a.get_contents(target=[], source=[], env=Environment())
-        assert c in matches, repr(c)
+        assert c in func_matches, repr(c)
 
         a = SCons.Action.FunctionAction(GlobalFunc, varlist=['XYZ'])
 
-        matches_foo = map(lambda x: x + "foo", matches)
+        matches_foo = map(lambda x: x + "foo", func_matches)
 
         c = a.get_contents(target=[], source=[], env=Environment())
-        assert c in matches, repr(c)
+        assert c in func_matches, repr(c)
         c = a.get_contents(target=[], source=[], env=Environment(XYZ = 'foo'))
         assert c in matches_foo, repr(c)
 
@@ -1525,7 +1530,7 @@ class FunctionActionTestCase(unittest.TestCase):
         lc = LocalClass()
         a = SCons.Action.FunctionAction(lc.LocalMethod)
         c = a.get_contents(target=[], source=[], env=Environment())
-        assert c in matches, repr(c)
+        assert c in meth_matches, repr(c)
 
     def test_strfunction(self):
         """Test the FunctionAction.strfunction() method
index 9b2b4b4b8776574845353d08fd04e31f51386057..7caee618d2aadf77c6afed6cae02ea1691f3595d 100644 (file)
@@ -34,6 +34,7 @@ import sys
 
 import SCons.Action
 
+cache_enabled = True
 cache_debug = False
 cache_force = False
 cache_show = False
@@ -129,31 +130,33 @@ class CacheDir:
         except ImportError:
             msg = "No hashlib or MD5 module available, CacheDir() not supported"
             SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
+            self.path = None
         else:
             self.path = path
+        self.current_cache_debug = None
+        self.debugFP = None
 
-    def CacheDebugWrite(self, fmt, target, cachefile):
-        self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
-
-    def CacheDebugQuiet(self, fmt, target, cachefile):
-        pass
-
-    def CacheDebugInit(self, fmt, target, cachefile):
-        if cache_debug:
+    def CacheDebug(self, fmt, target, cachefile):
+        if cache_debug != self.current_cache_debug:
             if cache_debug == '-':
                 self.debugFP = sys.stdout
-            else:
+            elif cache_debug:
                 self.debugFP = open(cache_debug, 'w')
-            self.CacheDebug = self.CacheDebugWrite
-            self.CacheDebug(fmt, target, cachefile)
-        else:
-            self.CacheDebug = self.CacheDebugQuiet
+            else:
+                self.debugFP = None
+            self.current_cache_debug = cache_debug
+        if self.debugFP:
+            self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
 
-    CacheDebug = CacheDebugInit
+    def is_enabled(self):
+        return (cache_enabled and not self.path is None)
 
     def cachepath(self, node):
         """
         """
+        if not self.is_enabled():
+            return None, None
+
         sig = node.get_cachedir_bsig()
         subdir = string.upper(sig[0])
         dir = os.path.join(self.path, subdir)
@@ -184,6 +187,9 @@ class CacheDir:
         execute the CacheRetrieveFunc and then have the latter
         explicitly check SCons.Action.execute_actions itself.
         """
+        if not self.is_enabled():
+            return False
+
         retrieved = False
 
         if cache_show:
@@ -202,16 +208,10 @@ class CacheDir:
         return retrieved
 
     def push(self, node):
+        if not self.is_enabled():
+            return
         return CachePush(node, [], node.get_build_env())
 
     def push_if_forced(self, node):
         if cache_force:
             return self.push(node)
-
-class Null(SCons.Util.Null):
-    def repr(self):
-        return 'CacheDir.Null()'
-    def cachepath(self, node):
-        return None, None
-    def retrieve(self, node):
-        return False
index fcf8c5a75bbb714397fad409b4e81fac4e747b5c..33899f60a6c67216c4d3a83442c2724af75b9eba 100644 (file)
@@ -371,11 +371,10 @@ int main()
 }
 """
 
-        # XXX: Try* vs CompileProg ?
-        st = context.TryCompile(src % (type_name, expect), suffix)
-        if st:
-            _Have(context, "SIZEOF_" + type_name, str(expect))
+        st = context.CompileProg(src % (type_name, expect), suffix)
+        if not st:
             context.Display("yes\n")
+            _Have(context, "SIZEOF_%s" % type_name, expect)
             return expect
         else:
             context.Display("no\n")
@@ -400,21 +399,76 @@ int main() {
     return 0;
 }
     """
-        ret = context.TryRun(src, suffix)
-        st = ret[0]
+        st, out = context.RunProg(src, suffix)
         try:
-            size = int(ret[1])
-            _Have(context, "SIZEOF_" + type_name, str(size))
-            context.Display("%d\n" % size)
+            size = int(out)
         except ValueError:
+            # If cannot convert output of test prog to an integer (the size),
+            # something went wront, so just fail
+            st = 1
             size = 0
-            _LogFailed(context, src, st)
-            context.Display(" Failed !\n")
-        if st:
+
+        if not st:
+            context.Display("yes\n")
+            _Have(context, "SIZEOF_%s" % type_name, size)
             return size
         else:
+            context.Display("no\n")
+            _LogFailed(context, src, st)
             return 0
 
+    return 0
+
+def CheckDeclaration(context, symbol, includes = None, language = None):
+    """Checks whether symbol is declared.
+
+    Use the same test as autoconf, that is test whether the symbol is defined
+    as a macro or can be used as an r-value.
+
+    Arguments:
+        symbol : str
+            the symbol to check
+        includes : str
+            Optional "header" can be defined to include a header file.
+        language : str
+            only C and C++ supported.
+
+    Returns:
+        status : bool
+            True if the check failed, False if succeeded."""
+    
+    # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
+    if context.headerfilename:
+        includetext = '#include "%s"' % context.headerfilename
+    else:
+        includetext = ''
+
+    if not includes:
+        includes = ""
+
+    lang, suffix, msg = _lang2suffix(language)
+    if msg:
+        context.Display("Cannot check for declaration %s: %s\n" % (type_name, msg))
+        return msg
+
+    src = includetext + includes 
+    context.Display('Checking whether %s is declared... ' % symbol)
+
+    src = src + r"""
+int main()
+{
+#ifndef %s
+    (void) %s;
+#endif
+    ;
+    return 0;
+}
+""" % (symbol, symbol)
+
+    st = context.CompileProg(src, suffix)
+    _YesNoResult(context, st, "HAVE_DECL_" + symbol, src)
+    return st
+
 def CheckLib(context, libs, func_name = None, header = None,
                  extra_libs = None, call = None, language = None, autoadd = 1):
     """
index c3d30cb2e20cf9f5fbca4380696a91ebd3af78f2..3cd47efe460738f39d75ec50e880e665d3d3f1b6 100644 (file)
@@ -93,7 +93,7 @@ def DefaultEnvironment(*args, **kw):
             _default_env.Decider('timestamp-match')
         global DefaultEnvironment
         DefaultEnvironment = _fetch_DefaultEnvironment
-        _default_env._CacheDir = SCons.CacheDir.Null()
+        _default_env._CacheDir_path = None
     return _default_env
 
 # Emitters for setting the shared attribute on object files,
@@ -270,7 +270,7 @@ def _concat_ixes(prefix, list, suffix, env):
 
     return result
 
-def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
+def _stripixes(prefix, list, suffix, stripprefixes, stripsuffixes, env, c=None):
     """
     This is a wrapper around _concat()/_concat_ixes() that checks for the
     existence of prefixes or suffixes on list elements and strips them
@@ -295,19 +295,39 @@ def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
     if SCons.Util.is_List(list):
         list = SCons.Util.flatten(list)
 
-    lsp = len(stripprefix)
-    lss = len(stripsuffix)
+    if SCons.Util.is_List(stripprefixes):
+        stripprefixes = map(env.subst, SCons.Util.flatten(stripprefixes))
+    else:
+        stripprefixes = [env.subst(stripprefixes)]
+
+    if SCons.Util.is_List(stripsuffixes):
+        stripsuffixes = map(env.subst, SCons.Util.flatten(stripsuffixes))
+    else:
+        stripsuffixes = [stripsuffixes]
+
     stripped = []
     for l in SCons.PathList.PathList(list).subst_path(env, None, None):
         if isinstance(l, SCons.Node.FS.File):
             stripped.append(l)
             continue
+
         if not SCons.Util.is_String(l):
             l = str(l)
-        if l[:lsp] == stripprefix:
-            l = l[lsp:]
-        if l[-lss:] == stripsuffix:
-            l = l[:-lss]
+
+        for stripprefix in stripprefixes:
+            lsp = len(stripprefix)
+            if l[:lsp] == stripprefix:
+                l = l[lsp:]
+                # Do not strip more than one prefix
+                break
+
+        for stripsuffix in stripsuffixes:
+            lss = len(stripsuffix)
+            if l[-lss:] == stripsuffix:
+                l = l[:-lss]
+                # Do not strip more than one suffix
+                break
+
         stripped.append(l)
 
     return c(prefix, stripped, suffix, env)
index cf2d0eb3cd72e88e4bd43af17f24f0cd63c2f048..02ad33244963ba6746e0dbbdeca6ccef35aab936 100644 (file)
@@ -481,7 +481,7 @@ class SubstitutionEnvironment:
                         # We have an object plus a string, or multiple
                         # objects that we need to smush together.  No choice
                         # but to make them into a string.
-                        p = string.join(map(SCons.Util.to_String, p), '')
+                        p = string.join(map(SCons.Util.to_String_for_subst, p), '')
             else:
                 p = s(p)
             r.append(p)
@@ -909,11 +909,18 @@ class Base(SubstitutionEnvironment):
 
     def get_CacheDir(self):
         try:
-            return self._CacheDir
+            path = self._CacheDir_path
         except AttributeError:
-            cd = SCons.Defaults.DefaultEnvironment()._CacheDir
-            self._CacheDir = cd
-            return cd
+            path = SCons.Defaults.DefaultEnvironment()._CacheDir_path
+        try:
+            if path == self._last_CacheDir_path:
+                return self._last_CacheDir
+        except AttributeError:
+            pass
+        cd = SCons.CacheDir.CacheDir(path)
+        self._last_CacheDir_path = path
+        self._last_CacheDir = cd
+        return cd
 
     def get_factory(self, factory, default='File'):
         """Return a factory function for creating Nodes for this
@@ -1645,10 +1652,9 @@ class Base(SubstitutionEnvironment):
 
     def CacheDir(self, path):
         import SCons.CacheDir
-        if path is None:
-            self._CacheDir = SCons.CacheDir.Null()
-        else:
-            self._CacheDir = SCons.CacheDir.CacheDir(self.subst(path))
+        if not path is None:
+            path = self.subst(path)
+        self._CacheDir_path = path
 
     def Clean(self, targets, files):
         global CleanTargets
index 3f64d43cf855316b2aa40356f94813814cd3c73c..4ffff7ad4680cb3e2c684f095abb45d6d838afad 100644 (file)
@@ -576,13 +576,13 @@ class SubstitutionTestCase(unittest.TestCase):
                           BAR=StringableObj("bar"))
 
         r = env.subst_path([ "${FOO}/bar", "${BAR}/baz" ])
-        assert r == [ "foo/bar", "bar/baz" ]
+        assert r == [ "foo/bar", "bar/baz" ], r
 
         r = env.subst_path([ "bar/${FOO}", "baz/${BAR}" ])
-        assert r == [ "bar/foo", "baz/bar" ]
+        assert r == [ "bar/foo", "baz/bar" ], r
 
         r = env.subst_path([ "bar/${FOO}/bar", "baz/${BAR}/baz" ])
-        assert r == [ "bar/foo/bar", "baz/bar/baz" ]
+        assert r == [ "bar/foo/bar", "baz/bar/baz" ], r
 
     def test_subst_target_source(self):
         """Test the base environment subst_target_source() method"""
@@ -764,40 +764,6 @@ sys.exit(1)
 
         d = env.ParseFlags(s)
 
-        if sys.version[:3] in ('1.5', '1.6', '2.0', '2.1', '2.2'):
-            # Pre-2.3 Python has no shlex.split() function.
-            # The compatibility layer does its best can by wrapping
-            # the old shlex.shlex class, but that class doesn't really
-            # understand quoting within the body of a token.  We're just
-            # going to live with this; it's the behavior they'd
-            # have anyway if they use the shlex module...
-            #
-            # (Note that we must test the actual Python version numbers
-            # above, not just test for whether trying to use shlex.split()
-            # throws an AttributeError, because the compatibility layer
-            # adds our wrapper function to the module as shlex.split().)
-
-            expect_CPPPATH = ['/usr/include/fum',
-                              'bar',
-                              '"C:\\Program']
-            expect_LIBPATH = ['/usr/fax',
-                              'foo',
-                              '"C:\\Program']
-            expect_LIBS = ['Files\\ASCEND\\include"',
-                           'xxx',
-                           'yyy',
-                           'Files\\ASCEND"',
-                           'ascend']
-        else:
-            expect_CPPPATH = ['/usr/include/fum',
-                              'bar',
-                              'C:\\Program Files\\ASCEND\\include']
-            expect_LIBPATH = ['/usr/fax',
-                              'foo',
-                              'C:\\Program Files\\ASCEND']
-            expect_LIBS = ['xxx', 'yyy', 'ascend']
-
-
         assert d['ASFLAGS'] == ['-as'], d['ASFLAGS']
         assert d['CFLAGS']  == ['-std=c99']
         assert d['CCFLAGS'] == ['-X', '-Wa,-as',
@@ -806,12 +772,16 @@ sys.exit(1)
                                   '+DD64'], d['CCFLAGS']
         assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES']
         assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS']
-        assert d['CPPPATH'] == expect_CPPPATH, d['CPPPATH']
+        assert d['CPPPATH'] == ['/usr/include/fum',
+                                'bar',
+                                'C:\\Program Files\\ASCEND\\include'], d['CPPPATH']
         assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH']
         assert d['FRAMEWORKS'] == ['Carbon'], d['FRAMEWORKS']
-        assert d['LIBPATH'] == expect_LIBPATH, d['LIBPATH']
+        assert d['LIBPATH'] == ['/usr/fax',
+                                'foo',
+                                'C:\\Program Files\\ASCEND'], d['LIBPATH']
         LIBS = map(str, d['LIBS'])
-        assert LIBS == expect_LIBS, (d['LIBS'], LIBS)
+        assert LIBS == ['xxx', 'yyy', 'ascend'], (d['LIBS'], LIBS)
         assert d['LINKFLAGS'] == ['-Wl,-link', '-pthread',
                                   '-mno-cygwin', '-mwindows',
                                   ('-arch', 'i386'),
@@ -2589,10 +2559,10 @@ def generate(env):
         env = self.TestEnvironment(CD = 'CacheDir')
 
         env.CacheDir('foo')
-        assert env._CacheDir.path == 'foo', env._CacheDir.path
+        assert env._CacheDir_path == 'foo', env._CacheDir_path
 
         env.CacheDir('$CD')
-        assert env._CacheDir.path == 'CacheDir', env._CacheDir.path
+        assert env._CacheDir_path == 'CacheDir', env._CacheDir_path
 
     def test_Clean(self):
         """Test the Clean() method"""
index 1cb0cf977248cbbd5cd6474c7266b783257c286c..72220424ae3ee9994ccccf5860f94e96c228c073 100644 (file)
@@ -335,13 +335,11 @@ class Null(_Executor):
     def get_build_env(self):
         import SCons.Util
         class NullEnvironment(SCons.Util.Null):
-            #def get_scanner(self, key):
-            #    return None
-            #def changed_since_last_build(self, dependency, target, prev_ni):
-            #    return dependency.changed_since_last_buld(target, prev_ni)
+            import SCons.CacheDir
+            _CacheDir_path = None
+            _CacheDir = SCons.CacheDir.CacheDir(None)
             def get_CacheDir(self):
-                import SCons.CacheDir
-                return SCons.CacheDir.Null()
+                return self._CacheDir
         return NullEnvironment()
     def get_build_scanner_path(self):
         return None
index b28aaaffed2891bdc92e09633c2f84c966ae9d4c..7b514092269b4d4dc57d4317f03ecf26c21175e4 100644 (file)
@@ -33,6 +33,18 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import SCons.compat
 
+
+# The default stack size (in kilobytes) of the threads used to execute
+# jobs in parallel.
+#
+# We use a stack size of 256 kilobytes. The default on some platforms
+# is too large and prevents us from creating enough threads to fully
+# parallelized the build. For example, the default stack size on linux
+# is 8 MBytes.
+
+default_stack_size = 256
+
+
 class Jobs:
     """An instance of this class initializes N jobs, and provides
     methods for starting, stopping, and waiting on all N jobs.
@@ -55,7 +67,12 @@ class Jobs:
         self.job = None
         if num > 1:
             try:
-                self.job = Parallel(taskmaster, num)
+                stack_size = SCons.Job.stack_size
+            except AttributeError:
+                stack_size = default_stack_size
+                
+            try:
+                self.job = Parallel(taskmaster, num, stack_size)
                 self.num_jobs = num
             except NameError:
                 pass
@@ -175,17 +192,40 @@ else:
     class ThreadPool:
         """This class is responsible for spawning and managing worker threads."""
 
-        def __init__(self, num):
-            """Create the request and reply queues, and 'num' worker threads."""
+        def __init__(self, num, stack_size):
+            """Create the request and reply queues, and 'num' worker threads.
+            
+            One must specify the stack size of the worker threads. The
+            stack size is specified in kilobytes.
+            """
             self.requestQueue = Queue.Queue(0)
             self.resultsQueue = Queue.Queue(0)
 
+            try:
+                prev_size = threading.stack_size(stack_size*1024) 
+            except AttributeError, e:
+                # Only print a warning if the stack size has been
+                # explicitely set.
+                if hasattr(SCons.Job, 'stack_size'):
+                    msg = "Setting stack size is unsupported by this version of Python:\n    " + \
+                        e.args[0]
+                    SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
+            except ValueError, e:
+                msg = "Setting stack size failed:\n    " + \
+                    e.message
+                SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
+
             # Create worker threads
             self.workers = []
             for _ in range(num):
                 worker = Worker(self.requestQueue, self.resultsQueue)
                 self.workers.append(worker)
 
+            # Once we drop Python 1.5 we can change the following to:
+            #if 'prev_size' in locals():
+            if 'prev_size' in locals().keys():
+                threading.stack_size(prev_size)
+
         def put(self, obj):
             """Put task into request queue."""
             self.requestQueue.put(obj)
@@ -233,7 +273,7 @@ else:
         This class is thread safe.
         """
 
-        def __init__(self, taskmaster, num):
+        def __init__(self, taskmaster, num, stack_size):
             """Create a new parallel job given a taskmaster.
 
             The taskmaster's next_task() method should return the next
@@ -249,7 +289,7 @@ else:
             multiple tasks simultaneously. """
 
             self.taskmaster = taskmaster
-            self.tp = ThreadPool(num)
+            self.tp = ThreadPool(num, stack_size)
 
             self.maxjobs = num
 
index 5f056e86453ecace6879a07f709ad7f45f4eef25..c4325812f1e0718eec994c0c9533df6eef6b8415 100644 (file)
@@ -293,7 +293,7 @@ class SerialTestCase(unittest.TestCase):
 class NoParallelTestCase(unittest.TestCase):
     def runTest(self):
         "test handling lack of parallel support"
-        def NoParallel(tm, num):
+        def NoParallel(tm, num, stack_size):
             raise NameError
         save_Parallel = SCons.Job.Parallel
         SCons.Job.Parallel = NoParallel
index c2b41818414e52618a4f8dee03ea4bc2967b7e3d..c4a5001e4b2542071edacefff898e6d1cf4e8d86 100644 (file)
@@ -217,33 +217,47 @@ class Memoizer:
 
 class M:
     def __init__(cls, name, bases, cls_dict):
-        cls.has_metaclass = 1
-
-class A:
-    __metaclass__ = M
+        cls.use_metaclass = 1
+        def fake_method(self):
+            pass
+        new.instancemethod(fake_method, None, cls)
 
 try:
-    has_metaclass = A.has_metaclass
+    class A:
+        __metaclass__ = M
+
+    use_metaclass = A.use_metaclass
 except AttributeError:
-    has_metaclass = None
+    use_metaclass = None
+    reason = 'no metaclasses'
+except TypeError:
+    use_metaclass = None
+    reason = 'new.instancemethod() bug'
+else:
+    del A
 
 del M
-del A
 
-if not has_metaclass:
+if not use_metaclass:
 
     def Dump(title):
         pass
 
-    class Memoized_Metaclass:
-        # Just a place-holder so pre-metaclass Python versions don't
-        # have to have special code for the Memoized classes.
-        pass
+    try:
+        class Memoized_Metaclass(type):
+            # Just a place-holder so pre-metaclass Python versions don't
+            # have to have special code for the Memoized classes.
+            pass
+    except TypeError:
+        class Memoized_Metaclass:
+            # A place-holder so pre-metaclass Python versions don't
+            # have to have special code for the Memoized classes.
+            pass
 
     def EnableMemoization():
         import SCons.Warnings
-        msg = 'memoization is not supported in this version of Python (no metaclasses)'
-        raise SCons.Warnings.NoMetaclassSupportWarning, msg
+        msg = 'memoization is not supported in this version of Python (%s)'
+        raise SCons.Warnings.NoMetaclassSupportWarning, msg % reason
 
 else:
 
index 7102f305081a92ced8c3851be77482bb8c6a5f90..bceeebf8851aad6b373cd1bc8ceb6c4e35bb1be7 100644 (file)
@@ -132,7 +132,7 @@ class CountDictTestCase(unittest.TestCase):
 
         c = obj.get_memoizer_counter('dict')
 
-        if SCons.Memoize.has_metaclass:
+        if SCons.Memoize.use_metaclass:
             assert c.hit == 3, c.hit
             assert c.miss == 2, c.miss
         else:
@@ -171,7 +171,7 @@ class CountValueTestCase(unittest.TestCase):
 
         c = obj.get_memoizer_counter('value')
 
-        if SCons.Memoize.has_metaclass:
+        if SCons.Memoize.use_metaclass:
             assert c.hit == 3, c.hit
             assert c.miss == 1, c.miss
         else:
index d0843d1fddea33c7ec3555d774ea61918269b2f2..1a3c0100c6eedc367dc1db70ac2d4eddca90a85b 100644 (file)
@@ -2469,38 +2469,20 @@ class File(Base):
             self.get_build_env().get_CacheDir().push_if_forced(self)
 
         ninfo = self.get_ninfo()
-        old = self.get_stored_info()
-
-        csig = None
-        mtime = self.get_timestamp()
-        size = self.get_size()
-
-        max_drift = self.fs.max_drift
-        if max_drift > 0:
-            if (time.time() - mtime) > max_drift:
-                try:
-                    n = old.ninfo
-                    if n.timestamp and n.csig and n.timestamp == mtime:
-                        csig = n.csig
-                except AttributeError:
-                    pass
-        elif max_drift == 0:
-            try:
-                csig = old.ninfo.csig
-            except AttributeError:
-                pass
 
+        csig = self.get_max_drift_csig()
         if csig:
             ninfo.csig = csig
 
-        ninfo.timestamp = mtime
-        ninfo.size = size
+        ninfo.timestamp = self.get_timestamp()
+        ninfo.size      = self.get_size()
 
         if not self.has_builder():
             # This is a source file, but it might have been a target file
             # in another build that included more of the DAG.  Copy
             # any build information that's stored in the .sconsign file
             # into our binfo object so it doesn't get lost.
+            old = self.get_stored_info()
             self.get_binfo().__dict__.update(old.binfo.__dict__)
 
         self.store_info()
@@ -2638,6 +2620,33 @@ class File(Base):
     # SIGNATURE SUBSYSTEM
     #
 
+    def get_max_drift_csig(self):
+        """
+        Returns the content signature currently stored for this node
+        if it's been unmodified longer than the max_drift value, or the
+        max_drift value is 0.  Returns None otherwise.
+        """
+        old = self.get_stored_info()
+        mtime = self.get_timestamp()
+
+        csig = None
+        max_drift = self.fs.max_drift
+        if max_drift > 0:
+            if (time.time() - mtime) > max_drift:
+                try:
+                    n = old.ninfo
+                    if n.timestamp and n.csig and n.timestamp == mtime:
+                        csig = n.csig
+                except AttributeError:
+                    pass
+        elif max_drift == 0:
+            try:
+                csig = old.ninfo.csig
+            except AttributeError:
+                pass
+
+        return csig
+
     def get_csig(self):
         """
         Generate a node's content signature, the digested signature
@@ -2653,16 +2662,19 @@ class File(Base):
         except AttributeError:
             pass
 
-        try:
-            contents = self.get_contents()
-        except IOError:
-            # This can happen if there's actually a directory on-disk,
-            # which can be the case if they've disabled disk checks,
-            # or if an action with a File target actually happens to
-            # create a same-named directory by mistake.
-            csig = ''
-        else:
-            csig = SCons.Util.MD5signature(contents)
+        csig = self.get_max_drift_csig()
+        if csig is None:
+
+            try:
+                contents = self.get_contents()
+            except IOError:
+                # This can happen if there's actually a directory on-disk,
+                # which can be the case if they've disabled disk checks,
+                # or if an action with a File target actually happens to
+                # create a same-named directory by mistake.
+                csig = ''
+            else:
+                csig = SCons.Util.MD5signature(contents)
 
         ninfo.csig = csig
 
@@ -2842,14 +2854,14 @@ class FileFinder:
         It would be more compact to just use this as a nested function
         with a default keyword argument (see the commented-out version
         below), but that doesn't work unless you have nested scopes,
-        so we define it here just this works work under Python 1.5.2.
+        so we define it here just so this work under Python 1.5.2.
         """
         if fd is None:
             fd = self.default_filedir
         dir, name = os.path.split(fd)
         drive, d = os.path.splitdrive(dir)
         if d in ('/', os.sep):
-            return p
+            return p.fs.get_root(drive).dir_on_disk(name)
         if dir:
             p = self.filedir_lookup(p, dir)
             if not p:
index fe42035d163340957df5ed248e380fcbada43342..8e9a3f8de7f6a0df1afbdc1adf1f310905036d69 100644 (file)
@@ -630,13 +630,13 @@ class NodeTestCase(unittest.TestCase):
 
         # XXX additional tests for the guts of the functionality some day
 
-    def test_del_binfo(self):
-        """Test deleting the build information from a Node
-        """
-        node = SCons.Node.Node()
-        node.binfo = None
-        node.del_binfo()
-        assert not hasattr(node, 'binfo'), node
+    #def test_del_binfo(self):
+    #    """Test deleting the build information from a Node
+    #    """
+    #    node = SCons.Node.Node()
+    #    node.binfo = None
+    #    node.del_binfo()
+    #    assert not hasattr(node, 'binfo'), node
 
     def test_store_info(self):
         """Test calling the method to store build information
index f252151547d2040187bdea48d4f0e15ed238e18d..4ca34e01db81453fdde0194826aaca97fe0c5e62 100644 (file)
@@ -375,7 +375,6 @@ class Node:
         # waiting for this Node to be built.
         for parent in self.waiting_parents.keys():
             parent.implicit = None
-            parent.del_binfo()
 
         self.clear()
 
@@ -433,14 +432,10 @@ class Node:
         can be re-evaluated by interfaces that do continuous integration
         builds).
         """
-        # Note in case it's important in the future:  We also used to clear
-        # the build information (the lists of dependencies) here like this:
-        #
-        #    self.del_binfo()
-        #
-        # But we now rely on the fact that we're going to look at that
-        # once before the build, and then store the results in the
-        # .sconsign file after the build.
+        # The del_binfo() call here isn't necessary for normal execution,
+        # but is for interactive mode, where we might rebuild the same
+        # target and need to start from scratch.
+        self.del_binfo()
         self.clear_memoized_values()
         self.ninfo = self.new_ninfo()
         self.executor_cleanup()
@@ -639,8 +634,6 @@ class Node:
                 # so we must recalculate the implicit deps:
                 self.implicit = []
                 self.implicit_dict = {}
-                self._children_reset()
-                self.del_binfo()
 
         # Have the executor scan the sources.
         executor.scan_sources(self.builder.source_scanner)
@@ -1013,6 +1006,7 @@ class Node:
             # entries to equal the new dependency list, for the benefit
             # of the loop below that updates node information.
             then.extend([None] * diff)
+            if t: Trace(': old %s new %s' % (len(then), len(children)))
             result = True
 
         for child, prev_ni in zip(children, then):
index be645ca2df847683f8d858cbd6afe316c45c977c..ae00fc0d5917c1acdd7bf370afe4f00854941366 100644 (file)
@@ -59,7 +59,7 @@ def node_conv(obj):
     try:
         get = obj.get
     except AttributeError:
-        if isinstance(obj, SCons.Node.Node):
+        if isinstance(obj, SCons.Node.Node) or SCons.Util.is_Sequence( obj ):
             result = obj
         else:
             result = str(obj)
@@ -132,10 +132,9 @@ class _PathList:
                 value = env.subst(value, target=target, source=source,
                                   conv=node_conv)
                 if SCons.Util.is_Sequence(value):
-                    # It came back as a string or tuple, which in this
-                    # case usually means some variable expanded to an
-                    # actually Dir node.  Concatenate the values.
-                    value = string.join(map(str, value), '')
+                    result.extend(value)
+                    continue
+                    
             elif type == TYPE_OBJECT:
                 value = node_conv(value)
             if value:
index 1d4e9f70d82d1e26b10325ff4498f480f3267739..afdabe1a793be12fc27e7d4403829cb7f6831caa 100644 (file)
@@ -235,7 +235,7 @@ def generate(env):
     env['LIBSUFFIX']      = '.a'
     env['SHLIBPREFIX']    = '$LIBPREFIX'
     env['SHLIBSUFFIX']    = '.so'
-    env['LIBPREFIXES']    = '$LIBPREFIX'
+    env['LIBPREFIXES']    = [ '$LIBPREFIX' ]
     env['LIBSUFFIXES']    = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
     env['PSPAWN']         = pspawn
     env['SPAWN']          = spawn
index ae3a77eb174b007da361b0294f6a51739ab66618..c5de498852c9b43ec1892880d74f6142ed09a0bd 100644 (file)
@@ -404,11 +404,12 @@ class SConfBase:
                  'CheckFunc'          : CheckFunc,
                  'CheckType'          : CheckType,
                  'CheckTypeSize'      : CheckTypeSize,
+                 'CheckDeclaration'   : CheckDeclaration,
                  'CheckHeader'        : CheckHeader,
                  'CheckCHeader'       : CheckCHeader,
                  'CheckCXXHeader'     : CheckCXXHeader,
                  'CheckLib'           : CheckLib,
-                 'CheckLibWithHeader' : CheckLibWithHeader
+                 'CheckLibWithHeader' : CheckLibWithHeader,
                }
         self.AddTests(default_tests)
         self.AddTests(custom_tests)
@@ -425,6 +426,31 @@ class SConfBase:
         self._shutdown()
         return self.env
 
+    def Define(self, name, value = None, comment = None):
+        """
+        Define a pre processor symbol name, with the optional given value in the
+        current config header.
+
+        If value is None (default), then #define name is written. If value is not
+        none, then #define name value is written.
+        
+        comment is a string which will be put as a C comment in the
+        header, to explain the meaning of the value (appropriate C comments /* and
+        */ will be put automatically."""
+        lines = []
+        if comment:
+            comment_str = "/* %s */" % comment
+            lines.append(comment_str)
+
+        if value is not None:
+            define_str = "#define %s %s" % (name, value)
+        else:
+            define_str = "#define %s" % name
+        lines.append(define_str)
+        lines.append('')
+
+        self.config_h_text = self.config_h_text + string.join(lines, '\n')
+
     def BuildNodes(self, nodes):
         """
         Tries to build the given nodes immediately. Returns 1 on success,
@@ -797,6 +823,12 @@ class CheckContext:
         # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
         return not self.TryBuild(self.env.Object, text, ext)
 
+    def RunProg(self, text, ext):
+        self.sconf.cached = 1
+        # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
+        st, out = self.TryRun(text, ext)
+        return not st, out
+
     def AppendLIBS(self, lib_name_list):
         oldLIBS = self.env.get( 'LIBS', [] )
         self.env.Append(LIBS = lib_name_list)
@@ -855,6 +887,13 @@ def CheckTypeSize(context, type_name, includes = "", language = None, expect = N
     context.did_show_result = 1
     return res
 
+def CheckDeclaration(context, declaration, includes = "", language = None):
+    res = SCons.Conftest.CheckDeclaration(context, declaration,
+                                          includes = includes, 
+                                          language = language)
+    context.did_show_result = 1
+    return not res
+
 def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'):
     # used by CheckHeader and CheckLibWithHeader to produce C - #include
     # statements from the specified header (list)
index 601c5eb433dd2d7290bba823ccbf7471deffcdef..f7d33f8e69c83cfe0d5773d99a9800d4ace0be6a 100644 (file)
@@ -497,6 +497,42 @@ int main() {
         finally:
             sconf.Finish()
 
+    def test_Define(self):
+        """Test SConf.Define()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'),
+                                 config_h = self.test.workpath('config.h'))
+        try:
+            # XXX: we test the generated config.h string. This is not so good,
+            # ideally, we would like to test if the generated file included in
+            # a test program does what we want.
+
+            # Test defining one symbol wo value
+            sconf.config_h_text = ''
+            sconf.Define('YOP')
+            assert sconf.config_h_text == '#define YOP\n'
+
+            # Test defining one symbol with integer value
+            sconf.config_h_text = ''
+            sconf.Define('YOP', 1)
+            assert sconf.config_h_text == '#define YOP 1\n'
+
+            # Test defining one symbol with string value
+            sconf.config_h_text = ''
+            sconf.Define('YOP', '"YIP"')
+            assert sconf.config_h_text == '#define YOP "YIP"\n'
+
+            # Test defining one symbol with string value
+            sconf.config_h_text = ''
+            sconf.Define('YOP', "YIP")
+            assert sconf.config_h_text == '#define YOP YIP\n'
+
+        finally:
+            sconf.Finish()
+
     def test_CheckTypeSize(self):
         """Test SConf.CheckTypeSize()
         """
@@ -531,6 +567,25 @@ int main() {
         finally:
             sconf.Finish()
 
+    def test_CheckDeclaration(self):
+        """Test SConf.CheckDeclaration()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+        try:
+            # In ANSI C, malloc should be available in stdlib
+            r = sconf.CheckDeclaration('malloc', includes = "#include <stdlib.h>")
+            assert r, "malloc not declared ??"
+            # For C++, __cplusplus should be declared 
+            r = sconf.CheckDeclaration('__cplusplus', language = 'C++')
+            assert r, "__cplusplus not declared in C++ ??"
+            r = sconf.CheckDeclaration('__cplusplus', language = 'C')
+            assert not r, "__cplusplus declared  in C ??"
+        finally:
+            sconf.Finish()
+
     def test_(self):
         """Test SConf.CheckType()
         """
index 276570e010e69e5c726a697d7c167d183a4970c6..4356c7a43d5aa716bced65f4ad8a603a212fd479 100644 (file)
@@ -31,10 +31,94 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import SCons.Node.FS
 import SCons.Scanner
+import SCons.Util
+
+import SCons.cpp
+
+class SConsCPPScanner(SCons.cpp.PreProcessor):
+    """
+    SCons-specific subclass of the cpp.py module's processing.
+
+    We subclass this so that: 1) we can deal with files represented
+    by Nodes, not strings; 2) we can keep track of the files that are
+    missing.
+    """
+    def __init__(self, *args, **kw):
+        apply(SCons.cpp.PreProcessor.__init__, (self,)+args, kw)
+        self.missing = []
+    def initialize_result(self, fname):
+        self.result = SCons.Util.UniqueList([fname])
+    def finalize_result(self, fname):
+        return self.result[1:]
+    def find_include_file(self, t):
+        keyword, quote, fname = t
+        result = SCons.Node.FS.find_file(fname, self.searchpath[quote])
+        if not result:
+            self.missing.append((fname, self.current_file))
+        return result
+    def read_file(self, file):
+        try:
+            fp = open(str(file.rfile()))
+        except EnvironmentError, e:
+            self.missing.append((file, self.current_file))
+            return ''
+        else:
+            return fp.read()
+
+def dictify_CPPDEFINES(env):
+    cppdefines = env.get('CPPDEFINES', {})
+    if cppdefines is None:
+        return {}
+    if SCons.Util.is_Sequence(cppdefines):
+        result = {}
+        for c in cppdefines:
+            if SCons.Util.is_Sequence(c):
+                result[c[0]] = c[1]
+            else:
+                result[c] = None
+        return result
+    if not SCons.Util.is_Dict(cppdefines):
+        return {cppdefines : None}
+    return cppdefines
+
+class SConsCPPScannerWrapper:
+    """
+    The SCons wrapper around a cpp.py scanner.
+
+    This is the actual glue between the calling conventions of generic
+    SCons scanners, and the (subclass of) cpp.py class that knows how
+    to look for #include lines with reasonably real C-preprocessor-like
+    evaluation of #if/#ifdef/#else/#elif lines.
+    """
+    def __init__(self, name, variable):
+        self.name = name
+        self.path = SCons.Scanner.FindPathDirs(variable)
+    def __call__(self, node, env, path = ()):
+        cpp = SConsCPPScanner(current = node.get_dir(),
+                              cpppath = path,
+                              dict = dictify_CPPDEFINES(env))
+        result = cpp(node)
+        for included, includer in cpp.missing:
+            fmt = "No dependency generated for file: %s (included from: %s) -- file not found"
+            SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
+                                fmt % (included, includer))
+        return result
+
+    def recurse_nodes(self, nodes):
+        return nodes
+    def select(self, node):
+        return self
 
 def CScanner():
     """Return a prototype Scanner instance for scanning source files
     that use the C pre-processor"""
+
+    # Here's how we would (or might) use the CPP scanner code above that
+    # knows how to evaluate #if/#ifdef/#else/#elif lines when searching
+    # for #includes.  This is commented out for now until we add the
+    # right configurability to let users pick between the scanners.
+    #return SConsCPPScannerWrapper("CScanner", "CPPPATH")
+
     cs = SCons.Scanner.ClassicCPP("CScanner",
                                   "$CPPSUFFIXES",
                                   "CPPPATH",
index 5a0b3834bc50543cfbab6cf4c230ee473a4d8707..bfbcd5de390a8c904ca3eac6521fb5c7a82f1a8b 100644 (file)
@@ -32,22 +32,37 @@ Coded by Andy Friesen
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import re
 import string
 
 import SCons.Scanner
 
 def DScanner():
     """Return a prototype Scanner instance for scanning D source files"""
-    ds = D(name = "DScanner",
-           suffixes = '$DSUFFIXES',
-           path_variable = 'DPATH',
-           regex = 'import\s+([^\;]*)\;')
+    ds = D()
     return ds
 
 class D(SCons.Scanner.Classic):
+    def __init__ (self):
+        SCons.Scanner.Classic.__init__ (self,
+            name = "DScanner",
+            suffixes = '$DSUFFIXES',
+            path_variable = 'DPATH',
+            regex = 'import\s+(?:[a-zA-Z0-9_.]+)\s*(?:,\s*(?:[a-zA-Z0-9_.]+)\s*)*;')
+
+        self.cre2 = re.compile ('(?:import\s)?\s*([a-zA-Z0-9_.]+)\s*(?:,|;)', re.M)
+
     def find_include(self, include, source_dir, path):
         # translate dots (package separators) to slashes
         inc = string.replace(include, '.', '/')
 
         i = SCons.Node.FS.find_file(inc + '.d', (source_dir,) + path)
+        if i is None:
+            i = SCons.Node.FS.find_file (inc + '.di', (source_dir,) + path)
         return i, include
+
+    def find_include_names(self, node):
+        includes = []
+        for i in self.cre.findall(node.get_contents()):
+            includes = includes + self.cre2.findall(i)
+        return includes
index c0a38b5609a243250fcd6d278df14810e751829b..ceb9bf52d60284208fb1b503891cf5863e264bb1 100644 (file)
@@ -56,7 +56,7 @@ class LaTeX(SCons.Scanner.Classic):
     but leave the file name untouched for "includegraphics." For
     the "bibliography" keyword we need to add .bib if there is
     no extension. (This need to be revisited since if there
-    is no extension for an :includegraphics" keyword latex will 
+    is no extension for an "includegraphics" keyword latex will 
     append .ps or .eps to find the file; while pdftex will use 
     other extensions.)
     """
index 64d6d77dfc1119824c787ffd2c4a714a619b83c5..6e9286a4b2640d9eed4d88ae054f58222b9c4aa7 100644 (file)
@@ -70,9 +70,12 @@ class FindPathDirsTestCase(unittest.TestCase):
 
         env = DummyEnvironment(LIBPATH = [ 'foo' ])
         env.fs = DummyFS()
+        env.fs._cwd = DummyNode('cwd')
 
         dir = DummyNode('dir', ['xxx'])
         fpd = SCons.Scanner.FindPathDirs('LIBPATH')
+        result = fpd(env)
+        assert str(result) == "('foo',)", result
         result = fpd(env, dir)
         assert str(result) == "('xxx', 'foo')", result
 
index c8ab1557f1ef580902d550d7fbeeace3d78566a5..924b2716be1e127fd5d1d2f9a917e6f5aa6015b2 100644 (file)
@@ -67,7 +67,7 @@ class FindPathDirs:
     will return all of the *path directories."""
     def __init__(self, variable):
         self.variable = variable
-    def __call__(self, env, dir, target=None, source=None, argument=None):
+    def __call__(self, env, dir=None, target=None, source=None, argument=None):
         import SCons.PathList
         try:
             path = env[self.variable]
@@ -346,13 +346,16 @@ class Classic(Current):
     def sort_key(self, include):
         return SCons.Node.FS._my_normcase(include)
 
+    def find_include_names(self, node):
+        return self.cre.findall(node.get_contents())
+
     def scan(self, node, path=()):
 
         # cache the includes list in node so we only scan it once:
         if node.includes != None:
             includes = node.includes
         else:
-            includes = self.cre.findall(node.get_contents())
+            includes = self.find_include_names (node)
             node.includes = includes
 
         # This is a hand-coded DSU (decorate-sort-undecorate, or
diff --git a/src/engine/SCons/Script/Interactive.py b/src/engine/SCons/Script/Interactive.py
new file mode 100644 (file)
index 0000000..e38c400
--- /dev/null
@@ -0,0 +1,359 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+__doc__ = """
+SCons interactive mode
+"""
+
+# TODO:
+#
+# This has the potential to grow into something with a really big life
+# of its own, which might or might not be a good thing.  Nevertheless,
+# here are some enhancements that will probably be requested some day
+# and are worth keeping in mind (assuming this takes off):
+# 
+# - A command to re-read / re-load the SConscript files.  This may
+#   involve allowing people to specify command-line options (e.g. -f,
+#   -I, --no-site-dir) that affect how the SConscript files are read.
+#
+# - Additional command-line options on the "build" command.
+#
+#   Of the supported options that seemed to make sense (after a quick
+#   pass through the list), the ones that seemed likely enough to be
+#   used are listed in the man page and have explicit test scripts.
+#
+#   These had code changed in Script/Main.py to support them, but didn't
+#   seem likely to be used regularly, so had no test scripts added:
+#
+#       build --diskcheck=*
+#       build --implicit-cache=*
+#       build --implicit-deps-changed=*
+#       build --implicit-deps-unchanged=*
+#
+#   These look like they should "just work" with no changes to the
+#   existing code, but like those above, look unlikely to be used and
+#   therefore had no test scripts added:
+#
+#       build --random
+#
+#   These I'm not sure about.  They might be useful for individual
+#   "build" commands, and may even work, but they seem unlikely enough
+#   that we'll wait until they're requested before spending any time on
+#   writing test scripts for them, or investigating whether they work.
+#
+#       build -q [???  is there a useful analog to the exit status?]
+#       build --duplicate=
+#       build --profile=
+#       build --max-drift=
+#       build --warn=*
+#       build --Y
+#
+# - Most of the SCons command-line options that the "build" command
+#   supports should be settable as default options that apply to all
+#   subsequent "build" commands.  Maybe a "set {option}" command that
+#   maps to "SetOption('{option}')".
+#
+# - Need something in the 'help' command that prints the -h output.
+#
+# - A command to run the configure subsystem separately (must see how
+#   this interacts with the new automake model).
+#
+# - Command-line completion of target names; maybe even of SCons options?
+#   Completion is something that's supported by the Python cmd module,
+#   so this should be doable without too much trouble.
+#
+
+import cmd
+import copy
+import os
+import re
+import shlex
+import string
+import sys
+
+try:
+    import readline
+except ImportError:
+    pass
+
+from SCons.Debug import Trace
+
+class SConsInteractiveCmd(cmd.Cmd):
+    """\
+    build [TARGETS]         Build the specified TARGETS and their dependencies.
+                            'b' is a synonym.
+    clean [TARGETS]         Clean (remove) the specified TARGETS and their
+                            dependencies.  'c' is a synonym.
+    exit                    Exit SCons interactive mode.
+    help [COMMAND]          Prints help for the specified COMMAND.  'h' and
+                            '?' are synonyms.
+    shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and '!'
+                            are synonyms.
+    version                 Prints SCons version information.
+    """
+
+    synonyms = {
+        'b'     : 'build',
+        'c'     : 'clean',
+        'h'     : 'help',
+        'scons' : 'build',
+        'sh'    : 'shell',
+    }
+
+    def __init__(self, **kw):
+        cmd.Cmd.__init__(self)
+        for key, val in kw.items():
+            setattr(self, key, val)
+
+        if sys.platform == 'win32':
+            self.shell_variable = 'COMSPEC'
+        else:
+            self.shell_variable = 'SHELL'
+
+    def default(self, argv):
+        print "*** Unknown command: %s" % argv[0]
+
+    def onecmd(self, line):
+        line = string.strip(line)
+        if not line:
+            print self.lastcmd
+            return self.emptyline()
+        self.lastcmd = line
+        if line[0] == '!':
+            line = 'shell ' + line[1:]
+        elif line[0] == '?':
+            line = 'help ' + line[1:]
+        argv = shlex.split(line)
+        argv[0] = self.synonyms.get(argv[0], argv[0])
+        if not argv[0]:
+            return self.default(line)
+        else:
+            try:
+                func = getattr(self, 'do_' + argv[0])
+            except AttributeError:
+                return self.default(argv)
+            return func(argv)
+
+    def do_build(self, argv):
+        """\
+        build [TARGETS]         Build the specified TARGETS and their
+                                dependencies.  'b' is a synonym.
+        """
+        import SCons.SConsign
+        import SCons.Script.Main
+
+        options = copy.deepcopy(self.options)
+
+        options, targets = self.parser.parse_args(argv[1:], values=options)
+
+        SCons.Script.COMMAND_LINE_TARGETS = targets
+
+        if targets:
+            SCons.Script.BUILD_TARGETS = targets
+        else:
+            # If the user didn't specify any targets on the command line,
+            # use the list of default targets.
+            SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default
+
+        nodes = SCons.Script.Main._build_targets(self.fs,
+                                                 options,
+                                                 targets,
+                                                 self.target_top)
+
+        if not nodes:
+            return
+
+        # Clean up so that we can perform the next build correctly.
+        #
+        # We do this by walking over all the children of the targets,
+        # and clearing their state.
+        #
+        # We currently have to re-scan each node to find their
+        # children, because built nodes have already been partially
+        # cleared and don't remember their children.  (In scons
+        # 0.96.1 and earlier, this wasn't the case, and we didn't
+        # have to re-scan the nodes.)
+        #
+        # Because we have to re-scan each node, we can't clear the
+        # nodes as we walk over them, because we may end up rescanning
+        # a cleared node as we scan a later node.  Therefore, only
+        # store the list of nodes that need to be cleared as we walk
+        # the tree, and clear them in a separate pass.
+        #
+        # XXX: Someone more familiar with the inner workings of scons
+        # may be able to point out a more efficient way to do this.
+
+        SCons.Script.Main.progress_display("scons: Clearing cached node information ...")
+
+        seen_nodes = {}
+
+        def get_unseen_children(node, parent, seen_nodes=seen_nodes):
+            def is_unseen(node, seen_nodes=seen_nodes):
+                return not seen_nodes.has_key(node)
+            return filter(is_unseen, node.children(scan=1))
+
+        def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes):
+            seen_nodes[node] = 1
+
+            # If this file is in a BuildDir and has a
+            # corresponding source file in the source tree, remember the
+            # node in the source tree, too.  This is needed in
+            # particular to clear cached implicit dependencies on the
+            # source file, since the scanner will scan it if the
+            # BuildDir was created with duplicate=0.
+            try:
+                rfile_method = node.rfile
+            except AttributeError:
+                return
+            else:
+                rfile = rfile_method()
+            if rfile != node:
+                seen_nodes[rfile] = 1
+
+        for node in nodes:
+            walker = SCons.Node.Walker(node,
+                                        kids_func=get_unseen_children,
+                                        eval_func=add_to_seen_nodes)
+            n = walker.next()
+            while n:
+                n = walker.next()
+
+        for node in seen_nodes.keys():
+            # Call node.clear() to clear most of the state
+            node.clear()
+            # node.clear() doesn't reset node.state, so call
+            # node.set_state() to reset it manually
+            node.set_state(SCons.Node.no_state)
+            node.implicit = None
+
+        SCons.SConsign.Reset()
+        SCons.Script.Main.progress_display("scons: done clearing node information.")
+
+    def do_clean(self, argv):
+        """\
+        clean [TARGETS]         Clean (remove) the specified TARGETS
+                                and their dependencies.  'c' is a synonym.
+        """
+        return self.do_build(['build', '--clean'] + argv[1:])
+
+    def do_EOF(self, argv):
+        print
+        self.do_exit(argv)
+
+    def _do_one_help(self, arg):
+        try:
+            # If help_<arg>() exists, then call it.
+            func = getattr(self, 'help_' + arg)
+        except AttributeError:
+            try:
+                func = getattr(self, 'do_' + arg)
+            except AttributeError:
+                doc = None
+            else:
+                doc = self._doc_to_help(func)
+            if doc:
+                sys.stdout.write(doc + '\n')
+                sys.stdout.flush()
+        else:
+            doc = self.strip_initial_spaces(func())
+            if doc:
+                sys.stdout.write(doc + '\n')
+                sys.stdout.flush()
+
+    def _doc_to_help(self, obj):
+        doc = obj.__doc__
+        if doc is None:
+            return ''
+        return self._strip_initial_spaces(doc)
+
+    def _strip_initial_spaces(self, s):
+        #lines = s.split('\n')
+        lines = string.split(s, '\n')
+        spaces = re.match(' *', lines[0]).group(0)
+        #def strip_spaces(l):
+        #    if l.startswith(spaces):
+        #        l = l[len(spaces):]
+        #    return l
+        #return '\n'.join([ strip_spaces(l) for l in lines ])
+        def strip_spaces(l, spaces=spaces):
+            if l[:len(spaces)] == spaces:
+                l = l[len(spaces):]
+            return l
+        lines = map(strip_spaces, lines)
+        return string.join(lines, '\n')
+
+    def do_exit(self, argv):
+        """\
+        exit                    Exit SCons interactive mode.
+        """
+        sys.exit(0)
+
+    def do_help(self, argv):
+        """\
+        help [COMMAND]          Prints help for the specified COMMAND.  'h'
+                                and '?' are synonyms.
+        """
+        if argv[1:]:
+            for arg in argv[1:]:
+                if self._do_one_help(arg):
+                    break
+        else:
+            # If bare 'help' is called, print this class's doc
+            # string (if it has one).
+            doc = self._doc_to_help(self.__class__)
+            if doc:
+                sys.stdout.write(doc + '\n')
+                sys.stdout.flush()
+
+    def do_shell(self, argv):
+        """\
+        shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and
+                                '!' are synonyms.
+        """
+        import subprocess
+        argv = argv[1:]
+        if not argv:
+            argv = os.environ[self.shell_variable]
+        try:
+            p = subprocess.Popen(argv)
+        except EnvironmentError, e:
+            sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror))
+        else:
+            p.wait()
+
+    def do_version(self, argv):
+        """\
+        version                 Prints SCons version information.
+        """
+        sys.stdout.write(self.parser.version + '\n')
+
+def interact(fs, parser, options, targets, target_top):
+    c = SConsInteractiveCmd(prompt = 'scons>>> ',
+                            fs = fs,
+                            parser = parser,
+                            options = options,
+                            targets = targets,
+                            target_top = target_top)
+    c.cmdloop()
index 97e0b190f455c3c113b817e9b1b7bdc48e548f30..bcbd0a14e93b02513a0098553e8a5b799d2b3f64 100644 (file)
@@ -68,6 +68,18 @@ import SCons.Taskmaster
 import SCons.Util
 import SCons.Warnings
 
+import SCons.Script.Interactive
+
+def fetch_win32_parallel_msg():
+    # A subsidiary function that exists solely to isolate this import
+    # so we don't have to pull it in on all platforms, and so that an
+    # in-line "import" statement in the _main() function below doesn't
+    # cause warnings about local names shadowing use of the 'SCons'
+    # globl in nest scopes and UnboundLocalErrors and the like in some
+    # versions (2.1) of Python.
+    import SCons.Platform.win32
+    SCons.Platform.win32.parallel_msg
+
 #
 
 class SConsPrintHelpException(Exception):
@@ -730,7 +742,6 @@ def version_string(label, module):
                   module.__buildsys__)
 
 def _main(parser):
-    import SCons
     global exit_status
 
     options = parser.values
@@ -750,7 +761,8 @@ def _main(parser):
                          SCons.Warnings.NoMetaclassSupportWarning,
                          SCons.Warnings.NoObjectCountWarning,
                          SCons.Warnings.NoParallelSupportWarning,
-                         SCons.Warnings.MisleadingKeywordsWarning, ]
+                         SCons.Warnings.MisleadingKeywordsWarning,
+                         SCons.Warnings.StackSizeWarning, ]
     for warning in default_warnings:
         SCons.Warnings.enableWarningClass(warning)
     SCons.Warnings._warningOut = _scons_internal_warning
@@ -835,10 +847,10 @@ def _main(parser):
     SCons.Node.implicit_cache = options.implicit_cache
     SCons.Node.implicit_deps_changed = options.implicit_deps_changed
     SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+
     if options.no_exec:
         SCons.SConf.dryrun = 1
         SCons.Action.execute_actions = None
-        CleanTask.execute = CleanTask.show
     if options.question:
         SCons.SConf.dryrun = 1
     if options.clean:
@@ -850,19 +862,6 @@ def _main(parser):
 
     if options.no_progress or options.silent:
         progress_display.set_mode(0)
-    if options.silent:
-        display.set_mode(0)
-    if options.silent:
-        SCons.Action.print_actions = None
-
-    if options.cache_disable:
-        SCons.CacheDir.CacheDir = SCons.Util.Null()
-    if options.cache_debug:
-        SCons.CacheDir.cache_debug = options.cache_debug
-    if options.cache_force:
-        SCons.CacheDir.cache_force = True
-    if options.cache_show:
-        SCons.CacheDir.cache_show = True
 
     if options.site_dir:
         _load_site_scons_dir(d, options.site_dir)
@@ -887,7 +886,18 @@ def _main(parser):
     SCons.Script._Add_Targets(targets + parser.rargs)
     SCons.Script._Add_Arguments(xmit_args)
 
-    sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+    # If stdout is not a tty, replace it with a wrapper object to call flush
+    # after every write.
+    #
+    # Tty devices automatically flush after every newline, so the replacement
+    # isn't necessary.  Furthermore, if we replace sys.stdout, the readline
+    # module will no longer work.  This affects the behavior during
+    # --interactive mode.  --interactive should only be used when stdin and
+    # stdout refer to a tty.
+    if not sys.stdout.isatty():
+        sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+    if not sys.stderr.isatty():
+        sys.stderr = SCons.Util.Unbuffered(sys.stderr)
 
     memory_stats.append('before reading SConscript files:')
     count_stats.append(('pre-', 'read'))
@@ -956,6 +966,47 @@ def _main(parser):
     SCons.Node.implicit_cache = options.implicit_cache
     SCons.Node.FS.set_duplicate(options.duplicate)
     fs.set_max_drift(options.max_drift)
+    if not options.stack_size is None:
+        SCons.Job.stack_size = options.stack_size
+
+    platform = SCons.Platform.platform_module()
+
+    if options.interactive:
+        SCons.Script.Interactive.interact(fs, OptionsParser, options,
+                                          targets, target_top)
+
+    else:
+
+        # Build the targets
+        nodes = _build_targets(fs, options, targets, target_top)
+        if not nodes:
+            exit_status = 2
+
+def _build_targets(fs, options, targets, target_top):
+
+    progress_display.set_mode(not (options.no_progress or options.silent))
+    display.set_mode(not options.silent)
+    SCons.Action.print_actions          = not options.silent
+    SCons.Action.execute_actions        = not options.no_exec
+    SCons.SConf.dryrun                  = options.no_exec
+
+    if options.diskcheck:
+        SCons.Node.FS.set_diskcheck(options.diskcheck)
+
+    _set_debug_values(options)
+    SCons.Node.implicit_cache = options.implicit_cache
+    SCons.Node.implicit_deps_changed = options.implicit_deps_changed
+    SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+
+    SCons.CacheDir.cache_enabled = not options.cache_disable
+    SCons.CacheDir.cache_debug = options.cache_debug
+    SCons.CacheDir.cache_force = options.cache_force
+    SCons.CacheDir.cache_show = options.cache_show
+
+    if options.no_exec:
+        CleanTask.execute = CleanTask.show
+    else:
+        CleanTask.execute = CleanTask.remove
 
     lookup_top = None
     if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
@@ -1003,7 +1054,7 @@ def _main(parser):
 
     if not targets:
         sys.stderr.write("scons: *** No targets specified and no Default() targets found.  Stop.\n")
-        sys.exit(2)
+        return None
 
     def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
         if isinstance(x, SCons.Node.Node):
@@ -1046,7 +1097,7 @@ def _main(parser):
             opening_message = "Cleaning targets ..."
             closing_message = "done cleaning targets."
             if options.keep_going:
-                closing_message = "done cleaning targets (errors occurred during clean)."
+                failure_message = "done cleaning targets (errors occurred during clean)."
             else:
                 failure_message = "cleaning terminated because of errors."
     except AttributeError:
@@ -1091,8 +1142,7 @@ def _main(parser):
             msg = "parallel builds are unsupported by this version of Python;\n" + \
                   "\tignoring -j or num_jobs option.\n"
         elif sys.platform == 'win32':
-            import SCons.Platform.win32
-            msg = SCons.Platform.win32.parallel_msg
+            msg = fetch_win32_parallel_msg()
         if msg:
             SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
 
@@ -1101,7 +1151,15 @@ def _main(parser):
 
     try:
         progress_display("scons: " + opening_message)
-        jobs.run()
+        try:
+            jobs.run()
+        except KeyboardInterrupt:
+            # If we are in interactive mode, a KeyboardInterrupt
+            # interrupts only this current run.  Return 'nodes' normally
+            # so that the outer loop can clean up the nodes and continue.
+            if options.interactive:
+                print "Build interrupted."
+                # Continue and return normally
     finally:
         jobs.cleanup()
         if exit_status:
@@ -1114,6 +1172,8 @@ def _main(parser):
     memory_stats.append('after building targets:')
     count_stats.append(('post-', 'build'))
 
+    return nodes
+
 def _exec_main(parser, values):
     sconsflags = os.environ.get('SCONSFLAGS', '')
     all_args = string.split(sconsflags) + sys.argv[1:]
index 46ece27332f0543d15a3d8cd9bb541e042479004..8f7116de8f23c053ecfb9c81d0ede69b82898cc9 100644 (file)
@@ -122,6 +122,7 @@ class SConsValues(optparse.Values):
         'no_exec',
         'num_jobs',
         'random',
+        'stack_size',
     ]
 
     def set_option(self, name, value):
@@ -163,6 +164,11 @@ class SConsValues(optparse.Values):
                 # Set this right away so it can affect the rest of the
                 # file/Node lookups while processing the SConscript files.
                 SCons.Node.FS.set_diskcheck(value)
+        elif name == 'stack_size':
+            try:
+                value = int(value)
+            except ValueError:
+                raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
 
         self.__SConscript_settings__[name] = value
 
@@ -466,6 +472,7 @@ def Parser(version):
                            usage="usage: scons [OPTION] [TARGET] ...",)
 
     op.preserve_unknown_options = True
+    op.version = version
 
     # Add the options to the parser we just created.
     #
@@ -667,6 +674,11 @@ def Parser(version):
                   action="callback", callback=opt_implicit_deps,
                   help="Ignore changes in implicit dependencies.")
 
+    op.add_option('--interact', '--interactive',
+                  dest='interactive', default=False,
+                  action="store_true",
+                  help="Run in interactive mode.")
+
     op.add_option('-j', '--jobs',
                   nargs=1, type="int",
                   dest="num_jobs", default=1,
@@ -730,6 +742,13 @@ def Parser(version):
                   help="Use DIR instead of the usual site_scons dir.",
                   metavar="DIR")
 
+    op.add_option('--stack-size',
+                  nargs=1, type="int",
+                  dest='stack_size',
+                  action="store",
+                  help="Set the stack size of the threads used to run jobs to N kilobytes.",
+                  metavar="N")
+
     op.add_option('--taskmastertrace',
                   nargs=1,
                   dest="taskmastertrace_file", default=None,
@@ -777,8 +796,8 @@ def Parser(version):
                   help="Search up directory tree for SConstruct,       "
                        "build Default() targets from local SConscript.")
 
-    def opt_version(option, opt, value, parser, version=version):
-        sys.stdout.write(version + '\n')
+    def opt_version(option, opt, value, parser):
+        sys.stdout.write(parser.version + '\n')
         sys.exit(0)
     op.add_option("-v", "--version",
                   action="callback", callback=opt_version,
index 989f1dd5f15555803842c9ffe9d0a96058396d45..7a565baab7aeb16877b104a6e84e814ac84bb9f8 100644 (file)
@@ -39,11 +39,11 @@ import UserString
 
 import SCons.Errors
 
-from SCons.Util import is_String, is_List, is_Tuple
+from SCons.Util import is_String, is_Sequence
 
 # Indexed by the SUBST_* constants below.
-_strconv = [SCons.Util.to_String,
-            SCons.Util.to_String,
+_strconv = [SCons.Util.to_String_for_subst,
+            SCons.Util.to_String_for_subst,
             SCons.Util.to_String_for_signature]
 
 
@@ -188,7 +188,7 @@ class NLWrapper:
         list = self.list
         if list is None:
             list = []
-        elif not is_List(list) and not is_Tuple(list):
+        elif not is_Sequence(list):
             list = [list]
         # The map(self.func) call is what actually turns
         # a list into appropriate proxies.
@@ -203,10 +203,10 @@ class Targets_or_Sources(UserList.UserList):
     wrapping a NLWrapper.  This class handles the different methods used
     to access the list, calling the NLWrapper to create proxies on demand.
 
-    Note that we subclass UserList.UserList purely so that the is_List()
-    function will identify an object of this class as a list during
-    variable expansion.  We're not really using any UserList.UserList
-    methods in practice.
+    Note that we subclass UserList.UserList purely so that the
+    is_Sequence() function will identify an object of this class as
+    a list during variable expansion.  We're not really using any
+    UserList.UserList methods in practice.
     """
     def __init__(self, nl):
         self.nl = nl
@@ -312,6 +312,25 @@ _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
 # Indexed by the SUBST_* constants above.
 _regex_remove = [ _rm, None, _remove ]
 
+def _rm_list(list):
+    #return [ l for l in list if not l in ('$(', '$)') ]
+    return filter(lambda l: not l in ('$(', '$)'), list)
+
+def _remove_list(list):
+    result = []
+    do_append = result.append
+    for l in list:
+        if l == '$(':
+            do_append = lambda x: None
+        elif l == '$)':
+            do_append = result.append
+        else:
+            do_append(l)
+    return result
+
+# Indexed by the SUBST_* constants above.
+_list_remove = [ _rm_list, None, _remove_list ]
+
 # Regular expressions for splitting strings and handling substitutions,
 # for use by the scons_subst() and scons_subst_list() functions:
 #
@@ -342,7 +361,8 @@ _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
 
 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
-    """Expand a string containing construction variable substitutions.
+    """Expand a string or list containing construction variable
+    substitutions.
 
     This is the work-horse function for substitutions in file names
     and the like.  The companion scons_subst_list() function (below)
@@ -427,11 +447,10 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
                     var = string.split(key, '.')[0]
                     lv[var] = ''
                     return self.substitute(s, lv)
-            elif is_List(s) or is_Tuple(s):
+            elif is_Sequence(s):
                 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
                     return conv(substitute(l, lvars))
-                r = map(func, s)
-                return string.join(r)
+                return map(func, s)
             elif callable(s):
                 try:
                     s = s(target=self.target,
@@ -458,6 +477,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
             separate tokens.
             """
             if is_String(args) and not isinstance(args, CmdStringHolder):
+                args = str(args)        # In case it's a UserString.
                 try:
                     def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
                         return conv(expand(match.group(1), lvars))
@@ -472,11 +492,10 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
                     result = []
                     for a in args:
                         result.append(self.conv(self.expand(a, lvars)))
-                    try:
-                        result = string.join(result, '')
-                    except TypeError:
-                        if len(result) == 1:
-                            result = result[0]
+                    if len(result) == 1:
+                        result = result[0]
+                    else:
+                        result = string.join(map(str, result), '')
                 return result
             else:
                 return self.expand(args, lvars)
@@ -524,6 +543,10 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
             # Compress strings of white space characters into
             # a single space.
             result = string.strip(_space_sep.sub(' ', result))
+    elif is_Sequence(result):
+        remove = _list_remove[mode]
+        if remove:
+            result = remove(result)
 
     return result
 
@@ -634,7 +657,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
                     lv[var] = ''
                     self.substitute(s, lv, 0)
                     self.this_word()
-            elif is_List(s) or is_Tuple(s):
+            elif is_Sequence(s):
                 for a in s:
                     self.substitute(a, lvars, 1)
                     self.next_word()
@@ -666,6 +689,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
             """
 
             if is_String(args) and not isinstance(args, CmdStringHolder):
+                args = str(args)        # In case it's a UserString.
                 args = _separate_args.findall(args)
                 for a in args:
                     if a[0] in ' \t\n\r\f\v':
@@ -827,18 +851,18 @@ def scons_subst_once(strSubst, env, key):
         a = match.group(1)
         if a in matchlist:
             a = val
-        if is_List(a) or is_Tuple(a):
+        if is_Sequence(a):
             return string.join(map(str, a))
         else:
             return str(a)
 
-    if is_List(strSubst) or is_Tuple(strSubst):
+    if is_Sequence(strSubst):
         result = []
         for arg in strSubst:
             if is_String(arg):
                 if arg in matchlist:
                     arg = val
-                    if is_List(arg) or is_Tuple(arg):
+                    if is_Sequence(arg):
                         result.extend(arg)
                     else:
                         result.append(arg)
index b6e5b71d4b603b63b98e2fe2eb225dc3e6fe31e5..c06416417362d691c92db853a86d89e493bc8dfe 100644 (file)
@@ -190,6 +190,7 @@ class SubstTestCase(unittest.TestCase):
             'T'         : ('x', 'y'),
             'CS'        : cs,
             'CL'        : cl,
+            'US'        : UserString.UserString('us'),
 
             # Test function calls within ${}.
             'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
@@ -317,6 +318,12 @@ class SubstTestCase(unittest.TestCase):
             '$CS',                  'cs',
             '$CL',                  'cl',
 
+            # Various uses of UserString.
+            UserString.UserString('x'),         'x',
+            UserString.UserString('$X'),        'x',
+            UserString.UserString('$US'),       'us',
+            '$US',                              'us',
+
             # Test function calls within ${}.
             '$FUNCCALL',            'a xc b',
 
@@ -404,9 +411,9 @@ class SubstTestCase(unittest.TestCase):
                "This is test",
 
             ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
-                "| $( a | b $) | c 1",
-                "| a | b | c 1",
-                "| | c 1",
+                ["|", "$(", "a", "|", "b", "$)", "|", "c", "1"],
+                ["|", "a", "|", "b", "|", "c", "1"],
+                ["|", "|", "c", "1"],
         ]
 
         gvars = env.Dictionary()
@@ -570,7 +577,7 @@ class SubstTestCase(unittest.TestCase):
         cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
 
         newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
-        assert newcmd == 'test foo bar call test', newcmd
+        assert newcmd == ['test', 'foo', 'bar', 'call', 'test'], newcmd
 
         cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
         assert len(cmd_list) == 1, cmd_list
@@ -653,6 +660,7 @@ class SubstTestCase(unittest.TestCase):
             'L'         : ['x', 'y'],
             'CS'        : cs,
             'CL'        : cl,
+            'US'        : UserString.UserString('us'),
 
             # Test function calls within ${}.
             'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
@@ -786,6 +794,16 @@ class SubstTestCase(unittest.TestCase):
             '$CL',                  [['cl']],
             ['$CL'],                [['cl']],
 
+            # Various uses of UserString.
+            UserString.UserString('x'),         [['x']],
+            [UserString.UserString('x')],       [['x']],
+            UserString.UserString('$X'),        [['x']],
+            [UserString.UserString('$X')],      [['x']],
+            UserString.UserString('$US'),       [['us']],
+            [UserString.UserString('$US')],     [['us']],
+            '$US',                              [['us']],
+            ['$US'],                            [['us']],
+
             # Test function calls within ${}.
             '$FUNCCALL',            [['a', 'xc', 'b']],
 
index 3bb4225d742636c2e884151eda4401bd1c138047..9db8138c7812ea8022f2ee05a8fbe9435e28a733 100644 (file)
@@ -562,21 +562,6 @@ class Taskmaster:
 
             childstate = map(lambda N: (N, N.get_state()), children)
 
-            # Skip this node if any of its children have failed.  This
-            # catches the case where we're descending a top-level target
-            # and one of our children failed while trying to be built
-            # by a *previous* descent of an earlier top-level target.
-            failed_children = filter(lambda I: I[1] == SCons.Node.failed,
-                                     childstate)
-            if failed_children:
-                node.set_state(SCons.Node.failed)
-                if S: S.child_failed = S.child_failed + 1
-                if T:
-                    c = map(str, failed_children)
-                    c.sort()
-                    T.write(' children failed:\n    %s\n' % c)
-                continue
-
             # Detect dependency cycles:
             pending_nodes = filter(lambda I: I[1] == SCons.Node.pending, childstate)
             if pending_nodes:
@@ -632,6 +617,35 @@ class Taskmaster:
                     T.write(' waiting on side effects:\n    %s\n' % c)
                 continue
 
+            # Skip this node if any of its children have failed.
+            #
+            # This catches the case where we're descending a top-level
+            # target and one of our children failed while trying to be
+            # built by a *previous* descent of an earlier top-level
+            # target.
+            #
+            # It can also occur if a node is reused in multiple
+            # targets. One first descends though the one of the
+            # target, the next time occurs through the other target.
+            #
+            # Note that we can only have failed_children if the
+            # --keep-going flag was used, because without it the build
+            # will stop before diving in the other branch.
+            #
+            # Note that even if one of the children fails, we still
+            # added the other children to the list of candidate nodes
+            # to keep on building (--keep-going).
+            failed_children = filter(lambda I: I[1] == SCons.Node.failed,
+                                     childstate)
+            if failed_children:
+                node.set_state(SCons.Node.failed)
+                if S: S.child_failed = S.child_failed + 1
+                if T:
+                    c = map(lambda I: str(I[0]), failed_children)
+                    c.sort()
+                    T.write(' children failed:\n    %s\n' % c)
+                continue
+
             # The default when we've gotten through all of the checks above:
             # this node is ready to be built.
             if S: S.build = S.build + 1
index a65a4aff60a07f2ff5cc873bbf38d12c1080dff3..532301f698580bfa4527368f928c35a3cd1f6211 100644 (file)
@@ -35,12 +35,14 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import SCons.Util
 
-import gnulink
+# Even though the Mac is based on the GNU toolchain, it doesn't understand
+# the -rpath option, so we use the "link" tool instead of "gnulink".
+import link
 
 def generate(env):
     """Add Builders and construction variables for applelink to an
     Environment."""
-    gnulink.generate(env)
+    link.generate(env)
 
     env['FRAMEWORKPATHPREFIX'] = '-F'
     env['_FRAMEWORKPATH'] = '${_concat(FRAMEWORKPATHPREFIX, FRAMEWORKPATH, "", __env__)}'
diff --git a/src/engine/SCons/Tool/gfortran.py b/src/engine/SCons/Tool/gfortran.py
new file mode 100644 (file)
index 0000000..f3db693
--- /dev/null
@@ -0,0 +1,62 @@
+"""SCons.Tool.gfortran
+
+Tool-specific initialization for gfortran, the GNU Fortran 95/Fortran
+2003 compiler.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Util
+
+import fortran
+
+def generate(env):
+    """Add Builders and construction variables for gfortran to an
+    Environment."""
+    fortran.generate(env)
+
+    # which one is the good one ? ifort uses _FORTRAND, ifl FORTRAN,
+    # aixf77 F77 ...
+    #env['_FORTRAND'] = 'gfortran'
+    env['FORTRAN'] = 'gfortran'
+
+    # XXX does this need to be set too ?
+    #env['SHFORTRAN'] = 'gfortran'
+
+    if env['PLATFORM'] in ['cygwin', 'win32']:
+        env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS')
+    else:
+        env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -fPIC')
+
+    # XXX; Link problems: we need to add -lgfortran somewhere...
+
+def exists(env):
+    return env.Detect('gfortran')
diff --git a/src/engine/SCons/Tool/gfortran.xml b/src/engine/SCons/Tool/gfortran.xml
new file mode 100644 (file)
index 0000000..ba0fe76
--- /dev/null
@@ -0,0 +1,15 @@
+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="gfortran">
+<summary>
+Sets construction variables for the GNU F95/F2003 GNU compiler.
+</summary>
+<sets>
+FORTRAN
+SHFORTRANFLAGS
+</sets>
+</tool>
index 673c8486dba3f921aa0faee30834d39d89831f09..02cc52af3b70d3faa33b653ccd8bc526617dc458 100644 (file)
@@ -41,11 +41,14 @@ is_win64 = is_windows and (os.environ['PROCESSOR_ARCHITECTURE'] == 'AMD64' or
                            (os.environ.has_key('PROCESSOR_ARCHITEW6432') and
                             os.environ['PROCESSOR_ARCHITEW6432'] == 'AMD64'))
 is_linux = sys.platform == 'linux2'
+is_mac     = sys.platform == 'darwin'
 
 if is_windows:
     import SCons.Tool.msvc
 elif is_linux:
     import SCons.Tool.gcc
+elif is_mac:
+    import SCons.Tool.gcc
 import SCons.Util
 import SCons.Warnings
 
@@ -106,6 +109,11 @@ def check_abi(abi):
                       'x86_64' : 'x86_64',
                       'em64t'  : 'x86_64',
                       'amd64'  : 'x86_64'}
+    if is_mac:
+        valid_abis = {'ia32'   : 'ia32',
+                      'x86'    : 'ia32',
+                      'x86_64' : 'x86_64',
+                      'em64t'  : 'x86_64'}
     try:
         abi = valid_abis[abi]
     except KeyError:
@@ -196,8 +204,22 @@ def get_all_compiler_versions():
                 if ok:
                     versions.append(subkey)
                 else:
-                    # Registry points to nonexistent dir.  Ignore this version.
-                    print "Ignoring "+str(get_intel_registry_value('ProductDir', subkey, 'IA32'))
+                    try:
+                        # Registry points to nonexistent dir.  Ignore this
+                        # version.
+                        value = get_intel_registry_value('ProductDir', subkey, 'IA32')
+                    except MissingRegistryError, e:
+
+                        # Registry key is left dangling (potentially
+                        # after uninstalling).
+
+                        print \
+                            "scons: *** Ignoring the registry key for the Intel compiler version %s.\n" \
+                            "scons: *** It seems that the compiler was uninstalled and that the registry\n" \
+                            "scons: *** was not cleaned up properly.\n" % subkey
+                    else:
+                        print "scons: *** Ignoring "+str(value)
+
                 i = i + 1
         except EnvironmentError:
             # no more subkeys
@@ -205,11 +227,22 @@ def get_all_compiler_versions():
     elif is_linux:
         for d in glob.glob('/opt/intel_cc_*'):
             # Typical dir here is /opt/intel_cc_80.
-            versions.append(re.search(r'cc_(.*)$', d).group(1))
+            m = re.search(r'cc_(.*)$', d)
+            if m:
+                versions.append(m.group(1))
+        for d in glob.glob('/opt/intel/cc*/*'):
+            # Typical dir here is /opt/intel/cc/9.0 for IA32,
+            # /opt/intel/cce/9.0 for EMT64 (AMD64)
+            m = re.search(r'([0-9.]+)$', d)
+            if m:
+                versions.append(m.group(1))
+    elif is_mac:
         for d in glob.glob('/opt/intel/cc*/*'):
             # Typical dir here is /opt/intel/cc/9.0 for IA32,
             # /opt/intel/cce/9.0 for EMT64 (AMD64)
-            versions.append(re.search(r'([0-9.]+)$', d).group(1))
+            m = re.search(r'([0-9.]+)$', d)
+            if m:
+                versions.append(m.group(1))
     versions = uniquify(versions)       # remove dups
     versions.sort(vercmp)
     return versions
@@ -229,7 +262,7 @@ def get_intel_compiler_top(version, abi):
         if not os.path.exists(os.path.join(top, "Bin", "icl.exe")):
             raise MissingDirError, \
                   "Can't find Intel compiler in %s"%(top)
-    elif is_linux:
+    elif is_mac or is_linux:
         # first dir is new (>=9.0) style, second is old (8.0) style.
         dirs=('/opt/intel/cc/%s', '/opt/intel_cc_%s')
         if abi == 'x86_64':
@@ -256,7 +289,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
                         If topdir is used, version and abi are ignored.
       verbose: (int)    if >0, prints compiler version used.
     """
-    if not (is_linux or is_windows):
+    if not (is_mac or is_linux or is_windows):
         # can't handle this platform
         return
 
@@ -264,6 +297,8 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
         SCons.Tool.msvc.generate(env)
     elif is_linux:
         SCons.Tool.gcc.generate(env)
+    elif is_mac:
+        SCons.Tool.gcc.generate(env)
 
     # if version is unspecified, use latest
     vlist = get_all_compiler_versions()
@@ -284,7 +319,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
     # alternatives are ia64 for Itanium, or amd64 or em64t or x86_64 (all synonyms here)
     abi = check_abi(abi)
     if abi is None:
-        if is_linux:
+        if is_mac or is_linux:
             # Check if we are on 64-bit linux, default to 64 then.
             uname_m = os.uname()[4]
             if uname_m == 'x86_64':
@@ -308,7 +343,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
         # on $PATH and the user is importing their env.
         class ICLTopDirWarning(SCons.Warnings.Warning):
             pass
-        if is_linux and not env.Detect('icc') or \
+        if (is_mac or is_linux) and not env.Detect('icc') or \
            is_windows and not env.Detect('icl'):
 
             SCons.Warnings.enableWarningClass(ICLTopDirWarning)
@@ -325,11 +360,14 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
 
     if topdir:
         if verbose:
-            print "Intel C compiler: using version '%s' (%g), abi %s, in '%s'"%\
-                  (version, linux_ver_normalize(version),abi,topdir)
+            print "Intel C compiler: using version %s (%g), abi %s, in '%s'"%\
+                  (repr(version), linux_ver_normalize(version),abi,topdir)
             if is_linux:
                 # Show the actual compiler version by running the compiler.
                 os.system('%s/bin/icc --version'%topdir)
+            if is_mac:
+                # Show the actual compiler version by running the compiler.
+                os.system('%s/bin/icc --version'%topdir)
 
         env['INTEL_C_COMPILER_TOP'] = topdir
         if is_linux:
@@ -339,11 +377,22 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
                    'LD_LIBRARY_PATH' : 'lib'}
             for p in paths:
                 env.PrependENVPath(p, os.path.join(topdir, paths[p]))
+        if is_mac:
+            paths={'INCLUDE'         : 'include',
+                   'LIB'             : 'lib',
+                   'PATH'            : 'bin',
+                   'LD_LIBRARY_PATH' : 'lib'}
+            for p in paths:
+                env.PrependENVPath(p, os.path.join(topdir, paths[p]))
         if is_windows:
             #       env key    reg valname   default subdir of top
             paths=(('INCLUDE', 'IncludeDir', 'Include'),
                    ('LIB'    , 'LibDir',     'Lib'),
                    ('PATH'   , 'BinDir',     'Bin'))
+            # We are supposed to ignore version if topdir is set, so set
+            # it to the emptry string if it's not already set.
+            if version is None:
+                version = ''
             # Each path has a registry entry, use that or default to subdir
             for p in paths:
                 try:
@@ -392,7 +441,9 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
 
         licdir = None
         for ld in [envlicdir, reglicdir]:
-            if ld and os.path.exists(ld):
+            # If the string contains an '@', then assume it's a network
+            # license (port@system) and good by definition.
+            if ld and (string.find(ld, '@') != -1 or os.path.exists(ld)):
                 licdir = ld
                 break
         if not licdir:
@@ -409,7 +460,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
         env['ENV']['INTEL_LICENSE_FILE'] = licdir
 
 def exists(env):
-    if not (is_linux or is_windows):
+    if not (is_mac or is_linux or is_windows):
         # can't handle this platform
         return 0
 
@@ -424,6 +475,8 @@ def exists(env):
             return env.Detect('icl')
         elif is_linux:
             return env.Detect('icc')
+        elif is_mac:
+            return env.Detect('icc')
     return detected
 
 # end of file
index 4f221c0477299ea88f9925454a10b0b2333e240b..6594ecc51fe917e4067b5bc0b9c16df562d1a56a 100644 (file)
@@ -38,19 +38,32 @@ import SCons.Util
 
 def jarSources(target, source, env, for_signature):
     """Only include sources that are not a manifest file."""
-    jarchdir = env.subst('$JARCHDIR', target=target, source=source)
-    if jarchdir:
-        jarchdir = env.fs.Dir(jarchdir)
+    try:
+        env['JARCHDIR']
+    except KeyError:
+        jarchdir_set = False
+    else:
+        jarchdir_set = True
+        jarchdir = env.subst('$JARCHDIR', target=target, source=source)
+        if jarchdir:
+            jarchdir = env.fs.Dir(jarchdir)
     result = []
     for src in source:
         contents = src.get_contents()
         if contents[:16] != "Manifest-Version":
-            if jarchdir:
+            if jarchdir_set:
+                _chdir = jarchdir
+            else:
+                try:
+                    _chdir = src.attributes.java_classdir
+                except AttributeError:
+                    _chdir = None
+            if _chdir:
                 # If we are changing the dir with -C, then sources should
                 # be relative to that directory.
-                src = SCons.Subst.Literal(src.get_path(jarchdir))
+                src = SCons.Subst.Literal(src.get_path(_chdir))
                 result.append('-C')
-                result.append(jarchdir)
+                result.append(_chdir)
             result.append(src)
     return result
 
index a0d730e87fb061ec0e3832e5852bdff0147e0fc1..9e8fefac872ccff8d382d4e95daebcc6fdb43f23 100644 (file)
@@ -34,6 +34,11 @@ If the &cv-link-JARCHDIR; value is set, the
 command will change to the specified directory using the
 <option>-C</option>
 option.
+If &cv-JARCHDIR; is not set explicitly,
+&SCons; will use the top of any subdirectory tree
+in which Java <filename>.class</filename>
+were built by the &b-link-Java; Builder.
+
 If the contents any of the source files begin with the string
 <literal>Manifest-Version</literal>,
 the file is assumed to be a manifest
index be1a81aa71790e672733a00165d37c1df3d1baa0..b60aa8753290f844025ac544bdb6666ab1270499 100644 (file)
@@ -44,6 +44,11 @@ def smart_link(source, target, env, for_signature):
         return '$CXX'
     return '$CC'
 
+def shlib_emitter(target, source, env):
+    for tgt in target:
+        tgt.attributes.shared = 1
+    return (target, source)
+
 def generate(env):
     """Add Builders and construction variables for gnulink to an Environment."""
     SCons.Tool.createSharedLibBuilder(env)
@@ -54,14 +59,14 @@ def generate(env):
     env['SHLINKCOM']   = '$SHLINK -o $TARGET $SHLINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
     # don't set up the emitter, cause AppendUnique will generate a list
     # starting with None :-(
-    #env['SHLIBEMITTER']= None
+    env.Append(SHLIBEMITTER = [shlib_emitter])
     env['SMARTLINK']   = smart_link
     env['LINK']        = "$SMARTLINK"
     env['LINKFLAGS']   = SCons.Util.CLVar('')
     env['LINKCOM']     = '$LINK -o $TARGET $LINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
     env['LIBDIRPREFIX']='-L'
     env['LIBDIRSUFFIX']=''
-    env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIX, LIBSUFFIX, __env__)}'
+    env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
     env['LIBLINKPREFIX']='-l'
     env['LIBLINKSUFFIX']=''
 
index 25f356469825fe35420ca1b86ae0470b43762a1a..42eabafb985ac9010fc0765cd05dd6dfbd3beb39 100644 (file)
@@ -76,6 +76,9 @@ def windowsShlinkSources(target, source, env, for_signature):
 def windowsLibEmitter(target, source, env):
     SCons.Tool.msvc.validate_vars(env)
 
+    extratargets = []
+    extrasources = []
+
     dll = env.FindIxes(target, "SHLIBPREFIX", "SHLIBSUFFIX")
     no_import_lib = env.get('no_import_lib', 0)
 
@@ -87,38 +90,44 @@ def windowsLibEmitter(target, source, env):
        not env.FindIxes(source, "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"):
 
         # append a def file to the list of sources
-        source.append(env.ReplaceIxes(dll,
-                                      "SHLIBPREFIX", "SHLIBSUFFIX",
-                                      "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"))
+        extrasources.append(
+            env.ReplaceIxes(dll,
+                            "SHLIBPREFIX", "SHLIBSUFFIX",
+                            "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"))
 
     version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
     if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0):
         # MSVC 8 automatically generates .manifest files that must be installed
-        target.append(env.ReplaceIxes(dll,
-                                      "SHLIBPREFIX", "SHLIBSUFFIX",
-                                      "WINDOWSSHLIBMANIFESTPREFIX", "WINDOWSSHLIBMANIFESTSUFFIX"))
+        extratargets.append(
+            env.ReplaceIxes(dll,
+                            "SHLIBPREFIX", "SHLIBSUFFIX",
+                            "WINDOWSSHLIBMANIFESTPREFIX", "WINDOWSSHLIBMANIFESTSUFFIX"))
 
     if env.has_key('PDB') and env['PDB']:
         pdb = env.arg2nodes('$PDB', target=target, source=source)[0]
-        target.append(pdb)
+        extratargets.append(pdb)
         target[0].attributes.pdb = pdb
 
     if not no_import_lib and \
        not env.FindIxes(target, "LIBPREFIX", "LIBSUFFIX"):
         # Append an import library to the list of targets.
-        target.append(env.ReplaceIxes(dll,
-                                      "SHLIBPREFIX", "SHLIBSUFFIX",
-                                      "LIBPREFIX", "LIBSUFFIX"))
+        extratargets.append(
+            env.ReplaceIxes(dll,
+                            "SHLIBPREFIX", "SHLIBSUFFIX",
+                            "LIBPREFIX", "LIBSUFFIX"))
         # and .exp file is created if there are exports from a DLL
-        target.append(env.ReplaceIxes(dll,
-                                      "SHLIBPREFIX", "SHLIBSUFFIX",
-                                      "WINDOWSEXPPREFIX", "WINDOWSEXPSUFFIX"))
+        extratargets.append(
+            env.ReplaceIxes(dll,
+                            "SHLIBPREFIX", "SHLIBSUFFIX",
+                            "WINDOWSEXPPREFIX", "WINDOWSEXPSUFFIX"))
 
-    return (target, source)
+    return (target+extratargets, source+extrasources)
 
 def prog_emitter(target, source, env):
     SCons.Tool.msvc.validate_vars(env)
 
+    extratargets = []
+
     exe = env.FindIxes(target, "PROGPREFIX", "PROGSUFFIX")
     if not exe:
         raise SCons.Errors.UserError, "An executable should have exactly one target with the suffix: %s" % env.subst("$PROGSUFFIX")
@@ -126,16 +135,17 @@ def prog_emitter(target, source, env):
     version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
     if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0):
         # MSVC 8 automatically generates .manifest files that have to be installed
-        target.append(env.ReplaceIxes(exe,
-                                      "PROGPREFIX", "PROGSUFFIX",
-                                      "WINDOWSPROGMANIFESTPREFIX", "WINDOWSPROGMANIFESTSUFFIX"))
+        extratargets.append(
+            env.ReplaceIxes(exe,
+                            "PROGPREFIX", "PROGSUFFIX",
+                            "WINDOWSPROGMANIFESTPREFIX", "WINDOWSPROGMANIFESTSUFFIX"))
 
     if env.has_key('PDB') and env['PDB']:
         pdb = env.arg2nodes('$PDB', target=target, source=source)[0]
-        target.append(pdb)
+        extratargets.append(pdb)
         target[0].attributes.pdb = pdb
 
-    return (target,source)
+    return (target+extratargets,source)
 
 def RegServerFunc(target, source, env):
     if env.has_key('register') and env['register']:
index 105f42e5dcc0b44b00d860294f77b32cceb098da..d67cddb77cf7e973e7cac9fa275b132181289928 100644 (file)
@@ -66,7 +66,7 @@ def checkMocIncluded(target, source, env):
     cpp = source[0]
     # looks like cpp.includes is cleared before the build stage :-(
     # not really sure about the path transformations (moc.cwd? cpp.cwd?) :-/
-    path = SCons.Defaults.CScan.path_function(env, moc.cwd)
+    path = SCons.Defaults.CScan.path(env, moc.cwd)
     includes = SCons.Defaults.CScan(cpp, env, path)
     if not moc in includes:
         SCons.Warnings.warn(
index 4b48e0b909c5947fad4a5b8f91147dd512e1da4c..ed5c8ee7b422521631791abb49eb670796362655 100644 (file)
@@ -79,9 +79,13 @@ def emit_rmic_classes(target, source, env):
         s.attributes.java_classname = classname
         slist.append(s)
 
+    stub_suffixes = ['_Stub']
+    if env.get('JAVAVERSION') == '1.4':
+        stub_suffixes.append('_Skel')
+
     tlist = []
     for s in source:
-        for suff in ['_Skel', '_Stub']:
+        for suff in stub_suffixes:
             fname = string.replace(s.attributes.java_classname, '.', os.sep) + \
                     suff + class_suffix
             t = target[0].File(fname)
index 8ca1b89ce4995918ae54dde8c3888a4171fd3c27..eba49a77436cf41a3d3e4cce484d8e8488b843f6 100644 (file)
@@ -50,7 +50,8 @@ def swigSuffixEmitter(env, source):
     else:
         return '$SWIGCFILESUFFIX'
 
-_reModule = re.compile(r'%module\s+(.+)')
+# Match '%module test', as well as '%module(directors="1") test'
+_reModule = re.compile(r'%module(?:\s*\(.*\))?\s+(.+)')
 
 def _swigEmitter(target, source, env):
     swigflags = env.subst("$SWIGFLAGS", target=target, source=source)
index bbae25e0e9a6d6de2d3ba3c9a1bf3ce4792bd3ed..c3156a3aa09878eef87ac1b1618d2d9262dc619b 100644 (file)
@@ -42,7 +42,7 @@ import SCons.Node
 import SCons.Node.FS
 import SCons.Util
 
-warning_rerun_re = re.compile("^LaTeX Warning:.*Rerun", re.MULTILINE)
+warning_rerun_re = re.compile('(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)', re.MULTILINE)
 
 rerun_citations_str = "^LaTeX Warning:.*\n.*Rerun to get citations correct"
 rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE)
@@ -76,26 +76,52 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
 
     basename = SCons.Util.splitext(str(source[0]))[0]
     basedir = os.path.split(str(source[0]))[0]
-
-    # Notice that all the filenames are not prefixed with the basedir.
-    # That's because the *COM variables have the cd command in the prolog.
-
-    bblfilename = basename + '.bbl'
+    basefile = os.path.split(str(basename))[1]
+    abspath = os.path.abspath(basedir)
+    targetbase = SCons.Util.splitext(str(target[0]))[0]
+    targetdir = os.path.split(str(target[0]))[0]
+
+    # Not sure if these environment changes should go here or make the
+    # user do them I undo all but TEXPICTS but there is still the side
+    # effect of creating the empty (':') entries in the environment.
+
+    def modify_env_var(env, var, abspath):
+        try:
+            save = env['ENV'][var]
+        except KeyError:
+            save = ':'
+            env['ENV'][var] = ''
+        if SCons.Util.is_List(env['ENV'][var]):
+            env['ENV'][var] = [abspath] + env['ENV'][var]
+        else:
+            env['ENV'][var] = abspath + os.pathsep + env['ENV'][var]
+        return save
+
+    texinputs_save = modify_env_var(env, 'TEXINPUTS', abspath)
+    bibinputs_save = modify_env_var(env, 'BIBINPUTS', abspath)
+    bstinputs_save = modify_env_var(env, 'BSTINPUTS', abspath)
+    texpicts_save = modify_env_var(env, 'TEXPICTS', abspath)
+
+    # Create these file names with the target directory since they will
+    # be made there.   That's because the *COM variables have the cd
+    # command in the prolog.
+
+    bblfilename = os.path.join(targetdir, basefile + '.bbl')
     bblContents = ""
     if os.path.exists(bblfilename):
         bblContents = open(bblfilename, "rb").read()
 
-    idxfilename = basename + '.idx'
+    idxfilename = os.path.join(targetdir, basefile + '.idx')
     idxContents = ""
     if os.path.exists(idxfilename):
         idxContents = open(idxfilename, "rb").read()
 
-    tocfilename = basename + '.toc'
+    tocfilename = os.path.join(targetdir, basefile + '.toc')
     tocContents = ""
     if os.path.exists(tocfilename):
         tocContents = open(tocfilename, "rb").read()
 
-    # Run LaTeX once to generate a new aux file.
+    # Run LaTeX once to generate a new aux file and log file.
     XXXLaTeXAction(target, source, env)
 
     # Decide if various things need to be run, or run again.  We check
@@ -104,7 +130,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
     # with stubs that don't necessarily generate all of the same files.
 
     # Read the log file to find all .aux files
-    logfilename = basename + '.log'
+    logfilename = os.path.join(targetbase + '.log')
     auxfiles = []
     if os.path.exists(logfilename):
         content = open(logfilename, "rb").read()
@@ -112,10 +138,11 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
 
     # Now decide if bibtex will need to be run.
     for auxfilename in auxfiles:
-        if os.path.exists(os.path.join(basedir, auxfilename)):
-            content = open(os.path.join(basedir, auxfilename), "rb").read()
+        target_aux = os.path.join(targetdir, auxfilename)
+        if os.path.exists(target_aux):
+            content = open(target_aux, "rb").read()
             if string.find(content, "bibdata") != -1:
-                bibfile = env.fs.File(basename)
+                bibfile = env.fs.File(targetbase)
                 BibTeXAction(bibfile, bibfile, env)
                 break
 
@@ -131,7 +158,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
     # Now decide if latex will need to be run again due to index.
     if os.path.exists(idxfilename) and idxContents != open(idxfilename, "rb").read():
         # We must run makeindex
-        idxfile = env.fs.File(basename)
+        idxfile = env.fs.File(targetbase)
         MakeIndexAction(idxfile, idxfile, env)
         must_rerun_latex = 1
 
@@ -139,7 +166,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
         XXXLaTeXAction(target, source, env)
 
     # Now decide if latex needs to be run yet again to resolve warnings.
-    logfilename = basename + '.log'
+    logfilename = targetbase + '.log'
     for _ in range(int(env.subst('$LATEXRETRIES'))):
         if not os.path.exists(logfilename):
             break
@@ -149,6 +176,15 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
            not undefined_references_re.search(content):
             break
         XXXLaTeXAction(target, source, env)
+
+    env['ENV']['TEXINPUTS'] = texinputs_save
+    env['ENV']['BIBINPUTS'] = bibinputs_save
+    env['ENV']['BSTINPUTS'] = bibinputs_save
+
+    # The TEXPICTS enviroment variable is needed by a dvi -> pdf step
+    # later on Mac OSX so leave it,
+    # env['ENV']['TEXPICTS']  = texpicts_save
+
     return 0
 
 def LaTeXAuxAction(target = None, source= None, env=None):
@@ -176,27 +212,29 @@ def TeXLaTeXFunction(target = None, source= None, env=None):
 
 def tex_emitter(target, source, env):
     base = SCons.Util.splitext(str(source[0]))[0]
-    target.append(base + '.aux')
-    env.Precious(base + '.aux')
-    target.append(base + '.log')
+    targetbase = SCons.Util.splitext(str(target[0]))[0]
+
+    target.append(targetbase + '.aux')
+    env.Precious(targetbase + '.aux')
+    target.append(targetbase + '.log')
     for f in source:
         content = f.get_contents()
         if tableofcontents_re.search(content):
-            target.append(base + '.toc')
-            env.Precious(base + '.toc')
+            target.append(targetbase + '.toc')
+            env.Precious(targetbase + '.toc')
         if makeindex_re.search(content):
-            target.append(base + '.ilg')
-            target.append(base + '.ind')
-            target.append(base + '.idx')
-            env.Precious(base + '.idx')
+            target.append(targetbase + '.ilg')
+            target.append(targetbase + '.ind')
+            target.append(targetbase + '.idx')
+            env.Precious(targetbase + '.idx')
         if bibliography_re.search(content):
-            target.append(base + '.bbl')
-            env.Precious(base + '.bbl')
-            target.append(base + '.blg')
+            target.append(targetbase + '.bbl')
+            env.Precious(targetbase + '.bbl')
+            target.append(targetbase + '.blg')
 
-    # read log file to get all output file (include .aux files)
-    logfilename = base + '.log'
-    dir, base_nodir = os.path.split(base)
+    # read log file to get all .aux files
+    logfilename = targetbase + '.log'
+    dir, base_nodir = os.path.split(targetbase)
     if os.path.exists(logfilename):
         content = open(logfilename, "rb").read()
         out_files = openout_re.findall(content)
index 34f60cb67599188016ee284e52b666518c277a39..0b648e816633e73ae5257912bb5444e3eae5122c 100644 (file)
@@ -54,7 +54,7 @@ def _yaccEmitter(target, source, env, ysuf, hsuf):
     # If -d is specified on the command line, yacc will emit a .h
     # or .hpp file with the same name as the .c or .cpp output file.
     if '-d' in flags:
-        target.append(targetBase + env.subst(hsuf))
+        target.append(targetBase + env.subst(hsuf, target=target, source=source))
 
     # If -g is specified on the command line, yacc will emit a .vcg
     # file with the same base name as the .y, .yacc, .ym or .yy file.
@@ -108,7 +108,14 @@ def generate(env):
     env['YACCFLAGS'] = SCons.Util.CLVar('')
     env['YACCCOM']   = '$YACC $YACCFLAGS -o $TARGET $SOURCES'
     env['YACCHFILESUFFIX'] = '.h'
-    env['YACCHXXFILESUFFIX'] = '.hpp'
+
+    if env['PLATFORM'] == 'darwin':
+        # Bison on Mac OS X just appends ".h" to the generated target .cc
+        # or .cpp file name.  Hooray for delayed expansion of variables.
+        env['YACCHXXFILESUFFIX'] = '${TARGET.suffix}.h'
+    else:
+        env['YACCHXXFILESUFFIX'] = '.hpp'
+
     env['YACCVCGFILESUFFIX'] = '.vcg'
 
 def exists(env):
index 2db06030dfcd6ec5932739a67113ea55310997f8..aa648b1e5d53bdd4bb4ba646621e09d9b4d1fd95 100644 (file)
@@ -87,7 +87,13 @@ file with the specified suffix,
 it exists to allow you to specify
 what suffix the parser generator will use of its own accord.
 The default value is
-<filename>.hpp</filename>.
+<filename>.hpp</filename>,
+except on Mac OS X,
+where the default is
+<filename>${TARGET.suffix}.h</filename>.
+because the default &bison; parser generator just
+appends <filename>.h</filename>
+to the name of the generated C++ file.
 </summary>
 </cvar>
 
index 258de0f1fb3fb66bd1c3788b3cc3d62485e039de..08ce1f2adcd6b7d0736006be53e182cb24e27698 100644 (file)
@@ -133,10 +133,17 @@ def to_String_for_signature(obj):
     try:
         f = obj.for_signature
     except AttributeError:
-        return to_String(obj)
+        return to_String_for_subst(obj)
     else:
         return f()
 
+def to_String_for_subst(s):
+    if is_Sequence( s ):
+        return string.join( map(to_String_for_subst, s) )
+    
+    return to_String( s )
+
+
 class CallableComposite(UserList):
     """A simple composite callable class that, when called, will invoke all
     of its contained callables with the same arguments."""
@@ -344,55 +351,80 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}):
 # Yes, all of this manual testing breaks polymorphism, and the real
 # Pythonic way to do all of this would be to just try it and handle the
 # exception, but handling the exception when it's not the right type is
-# too slow.
-#
-# The actual implementations here have been selected after timings
-# coded up in in bench/is_types.py (from the SCons source tree, see the
-# scons-src distribution).  Key results from those timings:
-#
-#   --  Storing the type of the object in a variable (t = type(obj))
-#       slows down the case where it's a native type and the first
-#       comparison will match, but nicely speeds up the case where
-#       it's a different native type.  Since that's going to be common,
-#       it's a good tradeoff.
-#
-#   --  The data show that calling isinstance() on an object that's
-#       a native type (dict, list or string) is expensive enough that
-#       checking up front for whether the object is of type InstanceType
-#       is a pretty big win, even though it does slow down the case
-#       where it really *is* an object instance a little bit.
-
-def is_Dict(obj):
-    t = type(obj)
-    return t is DictType or \
-           (t is InstanceType and isinstance(obj, UserDict))
-
-def is_List(obj):
-    t = type(obj)
-    return t is ListType \
-        or (t is InstanceType and isinstance(obj, UserList))
-
-def is_Sequence(obj):
-    t = type(obj)
-    return t is ListType \
-        or t is TupleType \
-        or (t is InstanceType and isinstance(obj, UserList))
-
-def is_Tuple(obj):
-    t = type(obj)
-    return t is TupleType
+# often too slow.
 
-if hasattr(types, 'UnicodeType'):
-    def is_String(obj):
+try:
+    class mystr(str):
+        pass
+except TypeError:
+    # An older Python version without new-style classes.
+    #
+    # The actual implementations here have been selected after timings
+    # coded up in in bench/is_types.py (from the SCons source tree,
+    # see the scons-src distribution), mostly against Python 1.5.2.
+    # Key results from those timings:
+    #
+    #   --  Storing the type of the object in a variable (t = type(obj))
+    #       slows down the case where it's a native type and the first
+    #       comparison will match, but nicely speeds up the case where
+    #       it's a different native type.  Since that's going to be
+    #       common, it's a good tradeoff.
+    #
+    #   --  The data show that calling isinstance() on an object that's
+    #       a native type (dict, list or string) is expensive enough
+    #       that checking up front for whether the object is of type
+    #       InstanceType is a pretty big win, even though it does slow
+    #       down the case where it really *is* an object instance a
+    #       little bit.
+    def is_Dict(obj):
+        t = type(obj)
+        return t is DictType or \
+               (t is InstanceType and isinstance(obj, UserDict))
+
+    def is_List(obj):
         t = type(obj)
-        return t is StringType \
-            or t is UnicodeType \
-            or (t is InstanceType and isinstance(obj, UserString))
+        return t is ListType \
+            or (t is InstanceType and isinstance(obj, UserList))
+
+    def is_Sequence(obj):
+        t = type(obj)
+        return t is ListType \
+            or t is TupleType \
+            or (t is InstanceType and isinstance(obj, UserList))
+
+    def is_Tuple(obj):
+        t = type(obj)
+        return t is TupleType
+
+    if hasattr(types, 'UnicodeType'):
+        def is_String(obj):
+            t = type(obj)
+            return t is StringType \
+                or t is UnicodeType \
+                or (t is InstanceType and isinstance(obj, UserString))
+    else:
+        def is_String(obj):
+            t = type(obj)
+            return t is StringType \
+                or (t is InstanceType and isinstance(obj, UserString))
 else:
+    # A modern Python version with new-style classes, so we can just use
+    # isinstance().
+    def is_Dict(obj):
+        return isinstance(obj, (dict, UserDict))
+
+    def is_List(obj):
+        return isinstance(obj, (list, UserList))
+
+    def is_Sequence(obj):
+        return isinstance(obj, (list, UserList, tuple))
+
+    def is_Tuple(obj):
+        return isinstance(obj, (tuple))
+
     def is_String(obj):
-        t = type(obj)
-        return t is StringType \
-            or (t is InstanceType and isinstance(obj, UserString))
+        # Empirically, Python versions with new-style classes all have unicode.
+        return isinstance(obj, (str, unicode, UserString))
 
 
 
index 3e8085bae8f58d1170d27818dba9cf9712a4ff53..44d6fa89c79a8cbaf2955731c92786794771ace8 100644 (file)
@@ -205,6 +205,13 @@ class UtilTestCase(unittest.TestCase):
     def test_is_Dict(self):
         assert is_Dict({})
         assert is_Dict(UserDict())
+        try:
+            class mydict(dict):
+                pass
+        except TypeError:
+            pass
+        else:
+            assert is_Dict(mydict({}))
         assert not is_Dict([])
         assert not is_Dict(())
         assert not is_Dict("")
@@ -215,6 +222,13 @@ class UtilTestCase(unittest.TestCase):
         assert is_List([])
         import UserList
         assert is_List(UserList.UserList())
+        try:
+            class mylist(list):
+                pass
+        except TypeError:
+            pass
+        else:
+            assert is_List(mylist([]))
         assert not is_List(())
         assert not is_List({})
         assert not is_List("")
@@ -231,12 +245,26 @@ class UtilTestCase(unittest.TestCase):
             pass
         else:
             assert is_String(UserString.UserString(''))
+        try:
+            class mystr(str):
+                pass
+        except TypeError:
+            pass
+        else:
+            assert is_String(mystr(''))
         assert not is_String({})
         assert not is_String([])
         assert not is_String(())
 
     def test_is_Tuple(self):
         assert is_Tuple(())
+        try:
+            class mytuple(tuple):
+                pass
+        except TypeError:
+            pass
+        else:
+            assert is_Tuple(mytuple(()))
         assert not is_Tuple([])
         assert not is_Tuple({})
         assert not is_Tuple("")
index b1d39ec6d8f1ffcc5cef07969413cfe01ca90535..53549595edb22b32c62c6661fe7577565d40d157 100644 (file)
@@ -73,6 +73,9 @@ class NoParallelSupportWarning(Warning):
 class ReservedVariableWarning(Warning):
     pass
 
+class StackSizeWarning(Warning):
+    pass
+
 _warningAsException = 0
 
 # The below is a list of 2-tuples.  The first element is a class object.
index 47ae3bea2abf5957ae095d7a87e1e40a77f87bdc..91e3776bc161c4a97451dcfeccb49c5378a54372 100644 (file)
@@ -158,20 +158,14 @@ import shlex
 try:
     shlex.split
 except AttributeError:
-    # Pre-2.3 Python has no shlex.split function.
-    def split(s, comments=False):
-        import StringIO
-        lex = shlex.shlex(StringIO.StringIO(s))
-        lex.wordchars = lex.wordchars + '/\\-+,=:'
-        result = []
-        while True:
-            tt = lex.get_token()
-            if not tt:
-                break
-            result.append(tt)
-        return result
-    shlex.split = split
-    del split
+    # Pre-2.3 Python has no shlex.split() function.
+    #
+    # The full white-space splitting semantics of shlex.split() are
+    # complicated to reproduce by hand, so just use a compatibility
+    # version of the shlex module cribbed from Python 2.5 with some
+    # minor modifications for older Python versions.
+    del shlex
+    import_as('_scons_shlex', 'shlex')
 
 try:
     import subprocess
diff --git a/src/engine/SCons/compat/_scons_shlex.py b/src/engine/SCons/compat/_scons_shlex.py
new file mode 100644 (file)
index 0000000..d6c1035
--- /dev/null
@@ -0,0 +1,319 @@
+# -*- coding: iso-8859-1 -*-
+"""A lexical analyzer class for simple shell-like syntaxes."""
+
+# Module and documentation by Eric S. Raymond, 21 Dec 1998
+# Input stacking and error message cleanup added by ESR, March 2000
+# push_source() and pop_source() made explicit by ESR, January 2001.
+# Posix compliance, split(), string arguments, and
+# iterator interface by Gustavo Niemeyer, April 2003.
+
+import os.path
+import sys
+#from collections import deque
+
+class deque:
+    def __init__(self):
+        self.data = []
+    def __len__(self):
+        return len(self.data)
+    def appendleft(self, item):
+        self.data.insert(0, item)
+    def popleft(self):
+        return self.data.pop(0)
+
+try:
+    basestring
+except NameError:
+    import types
+    def is_basestring(s):
+        return type(s) is types.StringType
+else:
+    def is_basestring(s):
+        return isinstance(s, basestring)
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+__all__ = ["shlex", "split"]
+
+class shlex:
+    "A lexical analyzer class for simple shell-like syntaxes."
+    def __init__(self, instream=None, infile=None, posix=False):
+        if is_basestring(instream):
+            instream = StringIO(instream)
+        if instream is not None:
+            self.instream = instream
+            self.infile = infile
+        else:
+            self.instream = sys.stdin
+            self.infile = None
+        self.posix = posix
+        if posix:
+            self.eof = None
+        else:
+            self.eof = ''
+        self.commenters = '#'
+        self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
+                          'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
+        if self.posix:
+            self.wordchars = self.wordchars + ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
+                               'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
+        self.whitespace = ' \t\r\n'
+        self.whitespace_split = False
+        self.quotes = '\'"'
+        self.escape = '\\'
+        self.escapedquotes = '"'
+        self.state = ' '
+        self.pushback = deque()
+        self.lineno = 1
+        self.debug = 0
+        self.token = ''
+        self.filestack = deque()
+        self.source = None
+        if self.debug:
+            print 'shlex: reading from %s, line %d' \
+                  % (self.instream, self.lineno)
+
+    def push_token(self, tok):
+        "Push a token onto the stack popped by the get_token method"
+        if self.debug >= 1:
+            print "shlex: pushing token " + repr(tok)
+        self.pushback.appendleft(tok)
+
+    def push_source(self, newstream, newfile=None):
+        "Push an input source onto the lexer's input source stack."
+        if is_basestring(newstream):
+            newstream = StringIO(newstream)
+        self.filestack.appendleft((self.infile, self.instream, self.lineno))
+        self.infile = newfile
+        self.instream = newstream
+        self.lineno = 1
+        if self.debug:
+            if newfile is not None:
+                print 'shlex: pushing to file %s' % (self.infile,)
+            else:
+                print 'shlex: pushing to stream %s' % (self.instream,)
+
+    def pop_source(self):
+        "Pop the input source stack."
+        self.instream.close()
+        (self.infile, self.instream, self.lineno) = self.filestack.popleft()
+        if self.debug:
+            print 'shlex: popping to %s, line %d' \
+                  % (self.instream, self.lineno)
+        self.state = ' '
+
+    def get_token(self):
+        "Get a token from the input stream (or from stack if it's nonempty)"
+        if self.pushback:
+            tok = self.pushback.popleft()
+            if self.debug >= 1:
+                print "shlex: popping token " + repr(tok)
+            return tok
+        # No pushback.  Get a token.
+        raw = self.read_token()
+        # Handle inclusions
+        if self.source is not None:
+            while raw == self.source:
+                spec = self.sourcehook(self.read_token())
+                if spec:
+                    (newfile, newstream) = spec
+                    self.push_source(newstream, newfile)
+                raw = self.get_token()
+        # Maybe we got EOF instead?
+        while raw == self.eof:
+            if not self.filestack:
+                return self.eof
+            else:
+                self.pop_source()
+                raw = self.get_token()
+        # Neither inclusion nor EOF
+        if self.debug >= 1:
+            if raw != self.eof:
+                print "shlex: token=" + repr(raw)
+            else:
+                print "shlex: token=EOF"
+        return raw
+
+    def read_token(self):
+        quoted = False
+        escapedstate = ' '
+        while True:
+            nextchar = self.instream.read(1)
+            if nextchar == '\n':
+                self.lineno = self.lineno + 1
+            if self.debug >= 3:
+                print "shlex: in state", repr(self.state), \
+                      "I see character:", repr(nextchar)
+            if self.state is None:
+                self.token = ''        # past end of file
+                break
+            elif self.state == ' ':
+                if not nextchar:
+                    self.state = None  # end of file
+                    break
+                elif nextchar in self.whitespace:
+                    if self.debug >= 2:
+                        print "shlex: I see whitespace in whitespace state"
+                    if self.token or (self.posix and quoted):
+                        break   # emit current token
+                    else:
+                        continue
+                elif nextchar in self.commenters:
+                    self.instream.readline()
+                    self.lineno = self.lineno + 1
+                elif self.posix and nextchar in self.escape:
+                    escapedstate = 'a'
+                    self.state = nextchar
+                elif nextchar in self.wordchars:
+                    self.token = nextchar
+                    self.state = 'a'
+                elif nextchar in self.quotes:
+                    if not self.posix:
+                        self.token = nextchar
+                    self.state = nextchar
+                elif self.whitespace_split:
+                    self.token = nextchar
+                    self.state = 'a'
+                else:
+                    self.token = nextchar
+                    if self.token or (self.posix and quoted):
+                        break   # emit current token
+                    else:
+                        continue
+            elif self.state in self.quotes:
+                quoted = True
+                if not nextchar:      # end of file
+                    if self.debug >= 2:
+                        print "shlex: I see EOF in quotes state"
+                    # XXX what error should be raised here?
+                    raise ValueError, "No closing quotation"
+                if nextchar == self.state:
+                    if not self.posix:
+                        self.token = self.token + nextchar
+                        self.state = ' '
+                        break
+                    else:
+                        self.state = 'a'
+                elif self.posix and nextchar in self.escape and \
+                     self.state in self.escapedquotes:
+                    escapedstate = self.state
+                    self.state = nextchar
+                else:
+                    self.token = self.token + nextchar
+            elif self.state in self.escape:
+                if not nextchar:      # end of file
+                    if self.debug >= 2:
+                        print "shlex: I see EOF in escape state"
+                    # XXX what error should be raised here?
+                    raise ValueError, "No escaped character"
+                # In posix shells, only the quote itself or the escape
+                # character may be escaped within quotes.
+                if escapedstate in self.quotes and \
+                   nextchar != self.state and nextchar != escapedstate:
+                    self.token = self.token + self.state
+                self.token = self.token + nextchar
+                self.state = escapedstate
+            elif self.state == 'a':
+                if not nextchar:
+                    self.state = None   # end of file
+                    break
+                elif nextchar in self.whitespace:
+                    if self.debug >= 2:
+                        print "shlex: I see whitespace in word state"
+                    self.state = ' '
+                    if self.token or (self.posix and quoted):
+                        break   # emit current token
+                    else:
+                        continue
+                elif nextchar in self.commenters:
+                    self.instream.readline()
+                    self.lineno = self.lineno + 1
+                    if self.posix:
+                        self.state = ' '
+                        if self.token or (self.posix and quoted):
+                            break   # emit current token
+                        else:
+                            continue
+                elif self.posix and nextchar in self.quotes:
+                    self.state = nextchar
+                elif self.posix and nextchar in self.escape:
+                    escapedstate = 'a'
+                    self.state = nextchar
+                elif nextchar in self.wordchars or nextchar in self.quotes \
+                    or self.whitespace_split:
+                    self.token = self.token + nextchar
+                else:
+                    self.pushback.appendleft(nextchar)
+                    if self.debug >= 2:
+                        print "shlex: I see punctuation in word state"
+                    self.state = ' '
+                    if self.token:
+                        break   # emit current token
+                    else:
+                        continue
+        result = self.token
+        self.token = ''
+        if self.posix and not quoted and result == '':
+            result = None
+        if self.debug > 1:
+            if result:
+                print "shlex: raw token=" + repr(result)
+            else:
+                print "shlex: raw token=EOF"
+        return result
+
+    def sourcehook(self, newfile):
+        "Hook called on a filename to be sourced."
+        if newfile[0] == '"':
+            newfile = newfile[1:-1]
+        # This implements cpp-like semantics for relative-path inclusion.
+        if is_basestring(self.infile) and not os.path.isabs(newfile):
+            newfile = os.path.join(os.path.dirname(self.infile), newfile)
+        return (newfile, open(newfile, "r"))
+
+    def error_leader(self, infile=None, lineno=None):
+        "Emit a C-compiler-like, Emacs-friendly error-message leader."
+        if infile is None:
+            infile = self.infile
+        if lineno is None:
+            lineno = self.lineno
+        return "\"%s\", line %d: " % (infile, lineno)
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        token = self.get_token()
+        if token == self.eof:
+            raise StopIteration
+        return token
+
+def split(s, comments=False):
+    lex = shlex(s, posix=True)
+    lex.whitespace_split = True
+    if not comments:
+        lex.commenters = ''
+    #return list(lex)
+    result = []
+    while True:
+        token = lex.get_token()
+        if token == lex.eof:
+            break
+        result.append(token)
+    return result
+
+if __name__ == '__main__':
+    if len(sys.argv) == 1:
+        lexer = shlex()
+    else:
+        file = sys.argv[1]
+        lexer = shlex(open(file), file)
+    while 1:
+        tt = lexer.get_token()
+        if tt:
+            print "Token: " + repr(tt)
+        else:
+            break
index 8620936f53c6882ce981d198a0bfbe79f06acfa9..cdd6a3a9350ac4c96dbff177c82d58b0642834df 100644 (file)
@@ -45,11 +45,16 @@ import string
 # that we want to fetch, using the regular expressions to which the lists
 # of preprocessor directives map.
 cpp_lines_dict = {
-    # Fetch the rest of a #if/#elif/#ifdef/#ifndef/#import/#include/
-    # #include_next line as one argument.
-    ('if', 'elif', 'ifdef', 'ifndef', 'import', 'include', 'include_next',)
+    # Fetch the rest of a #if/#elif/#ifdef/#ifndef as one argument,
+    # separated from the keyword by white space.
+    ('if', 'elif', 'ifdef', 'ifndef',)
                         : '\s+(.+)',
 
+    # Fetch the rest of a #import/#include/#include_next line as one
+    # argument, with white space optional.
+    ('import', 'include', 'include_next',)
+                        : '\s*(.+)',
+
     # We don't care what comes after a #else or #endif line.
     ('else', 'endif',)  : '',
 
@@ -183,7 +188,13 @@ class FunctionEvaluator:
         """
         self.name = name
         self.args = function_arg_separator.split(args)
-        self.expansion = string.split(expansion, '##')
+        try:
+            expansion = string.split(expansion, '##')
+        except (AttributeError, TypeError):
+            # Python 1.5 throws TypeError if "expansion" isn't a string,
+            # later versions throw AttributeError.
+            pass
+        self.expansion = expansion
     def __call__(self, *values):
         """
         Evaluates the expansion of a #define macro function called
@@ -228,12 +239,14 @@ class PreProcessor:
     """
     The main workhorse class for handling C pre-processing.
     """
-    def __init__(self, current='.', cpppath=[], dict={}, all=0):
+    def __init__(self, current=os.curdir, cpppath=(), dict={}, all=0):
         global Table
 
+        cpppath = tuple(cpppath)
+
         self.searchpath = {
-            '"' :       [current] + cpppath,
-            '<' :       cpppath + [current],
+            '"' :       (current,) + cpppath,
+            '<' :       cpppath + (current,),
         }
 
         # Initialize our C preprocessor namespace for tracking the
@@ -254,7 +267,9 @@ class PreProcessor:
         # stack and changing what method gets called for each relevant
         # directive we might see next at this level (#else, #elif).
         # #endif will simply pop the stack.
-        d = {}
+        d = {
+            'scons_current_file'    : self.scons_current_file
+        }
         for op in Table.keys():
             d[op] = getattr(self, 'do_' + op)
         self.default_table = d
@@ -278,25 +293,34 @@ class PreProcessor:
                            (m[0],) + t[m[0]].match(m[1]).groups(),
                     cpp_tuples)
 
-    def __call__(self, contents):
+    def __call__(self, file):
+        """
+        Pre-processes a file.
+
+        This is the main public entry point.
+        """
+        self.current_file = file
+        return self.process_contents(self.read_file(file), file)
+
+    def process_contents(self, contents, fname=None):
         """
         Pre-processes a file contents.
 
-        This is the main entry point, which
+        This is the main internal entry point.
         """
         self.stack = []
         self.dispatch_table = self.default_table.copy()
+        self.current_file = fname
         self.tuples = self.tupleize(contents)
 
-        self.result = []
+        self.initialize_result(fname)
         while self.tuples:
             t = self.tuples.pop(0)
             # Uncomment to see the list of tuples being processed (e.g.,
             # to validate the CPP lines are being translated correctly).
             #print t
             self.dispatch_table[t[0]](t)
-
-        return self.result
+        return self.finalize_result(fname)
 
     # Dispatch table stack manipulation methods.
 
@@ -325,6 +349,9 @@ class PreProcessor:
         """
         pass
 
+    def scons_current_file(self, t):
+        self.current_file = t[1]
+
     def eval_expression(self, t):
         """
         Evaluates a C preprocessor expression.
@@ -337,17 +364,29 @@ class PreProcessor:
         try: return eval(t, self.cpp_namespace)
         except (NameError, TypeError): return 0
 
+    def initialize_result(self, fname):
+        self.result = [fname]
+
+    def finalize_result(self, fname):
+        return self.result[1:]
+
     def find_include_file(self, t):
         """
         Finds the #include file for a given preprocessor tuple.
         """
         fname = t[2]
         for d in self.searchpath[t[1]]:
-            f = os.path.join(d, fname)
+            if d == os.curdir:
+                f = fname
+            else:
+                f = os.path.join(d, fname)
             if os.path.isfile(f):
                 return f
         return None
 
+    def read_file(self, file):
+        return open(file).read()
+
     # Start and stop processing include lines.
 
     def start_handling_includes(self, t=None):
@@ -478,8 +517,10 @@ class PreProcessor:
         if include_file:
             #print "include_file =", include_file
             self.result.append(include_file)
-            contents = open(include_file).read()
-            new_tuples = self.tupleize(contents)
+            contents = self.read_file(include_file)
+            new_tuples = [('scons_current_file', include_file)] + \
+                         self.tupleize(contents) + \
+                         [('scons_current_file', self.current_file)]
             self.tuples[:] = new_tuples + self.tuples
 
     # Date: Tue, 22 Nov 2005 20:26:09 -0500
index 0959e2ccf1a51d0e7252355f59604d8171ce2394..33fd01d7b967b976c81910610f1ae1d4d5aa1ab4 100644 (file)
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import string
 import sys
 import unittest
 
-print sys.path
 import cpp
 
 
@@ -297,6 +297,9 @@ macro_function_input = """
 
 #include FUNC39c(ZERO, ONE)
 #include FUNC40c(ZERO, ONE)
+
+/* Make sure we don't die if the expansion isn't a string. */
+#define FUNC_INTEGER(x)       1
 """
 
 
@@ -312,6 +315,12 @@ token_pasting_input = """
 """
 
 
+no_space_input = """
+#include<file43-yes>
+#include"file44-yes"
+"""
+
+
 
 #    pp_class = PreProcessor
 #    #pp_class = DumbPreProcessor
@@ -331,55 +340,61 @@ class cppTestCase(unittest.TestCase):
     def test_basic(self):
         """Test basic #include scanning"""
         expect = self.basic_expect
-        result = self.cpp(basic_input)
+        result = self.cpp.process_contents(basic_input)
         assert expect == result, (expect, result)
 
     def test_substitution(self):
         """Test substitution of #include files using CPP variables"""
         expect = self.substitution_expect
-        result = self.cpp(substitution_input)
+        result = self.cpp.process_contents(substitution_input)
         assert expect == result, (expect, result)
 
     def test_ifdef(self):
         """Test basic #ifdef processing"""
         expect = self.ifdef_expect
-        result = self.cpp(ifdef_input)
+        result = self.cpp.process_contents(ifdef_input)
         assert expect == result, (expect, result)
 
     def test_if_boolean(self):
         """Test #if with Boolean values"""
         expect = self.if_boolean_expect
-        result = self.cpp(if_boolean_input)
+        result = self.cpp.process_contents(if_boolean_input)
         assert expect == result, (expect, result)
 
     def test_if_defined(self):
         """Test #if defined() idioms"""
         expect = self.if_defined_expect
-        result = self.cpp(if_defined_input)
+        result = self.cpp.process_contents(if_defined_input)
         assert expect == result, (expect, result)
 
     def test_expression(self):
         """Test #if with arithmetic expressions"""
         expect = self.expression_expect
-        result = self.cpp(expression_input)
+        result = self.cpp.process_contents(expression_input)
         assert expect == result, (expect, result)
 
     def test_undef(self):
         """Test #undef handling"""
         expect = self.undef_expect
-        result = self.cpp(undef_input)
+        result = self.cpp.process_contents(undef_input)
         assert expect == result, (expect, result)
 
     def test_macro_function(self):
         """Test using macro functions to express file names"""
         expect = self.macro_function_expect
-        result = self.cpp(macro_function_input)
+        result = self.cpp.process_contents(macro_function_input)
         assert expect == result, (expect, result)
 
     def test_token_pasting(self):
-        """Test taken-pasting to construct file names"""
+        """Test token-pasting to construct file names"""
         expect = self.token_pasting_expect
-        result = self.cpp(token_pasting_input)
+        result = self.cpp.process_contents(token_pasting_input)
+        assert expect == result, (expect, result)
+
+    def test_no_space(self):
+        """Test no space between #include and the quote"""
+        expect = self.no_space_expect
+        result = self.cpp.process_contents(no_space_input)
         assert expect == result, (expect, result)
 
 class cppAllTestCase(cppTestCase):
@@ -460,6 +475,11 @@ class PreProcessorTestCase(cppAllTestCase):
         ('include', '<', 'file42-yes'),
     ]
 
+    no_space_expect = [
+        ('include', '<', 'file43-yes'),
+        ('include', '"', 'file44-yes'),
+    ]
+
 class DumbPreProcessorTestCase(cppAllTestCase):
     cpp_class = cpp.DumbPreProcessor
 
@@ -560,14 +580,126 @@ class DumbPreProcessorTestCase(cppAllTestCase):
         ('include', '<', 'file42-yes'),
     ]
 
+    no_space_expect = [
+        ('include', '<', 'file43-yes'),
+        ('include', '"', 'file44-yes'),
+    ]
+
+
+
+import os
+import re
+import shutil
+import tempfile
+
+tempfile.template = 'cppTests.'
+if os.name in ('posix', 'nt'):
+    tempfile.template = 'cppTests.' + str(os.getpid()) + '.'
+else:
+    tempfile.template = 'cppTests.'
+
+_Cleanup = []
+
+def _clean():
+    for dir in _Cleanup:
+        if os.path.exists(dir):
+            shutil.rmtree(dir)
+
+sys.exitfunc = _clean
+
+class fileTestCase(unittest.TestCase):
+    cpp_class = cpp.DumbPreProcessor
+
+    def setUp(self):
+        try:
+            path = tempfile.mktemp(prefix=tempfile.template)
+        except TypeError:
+            # The tempfile.mktemp() function in earlier versions of Python
+            # has no prefix argument, but uses the tempfile.template
+            # value that we set above.
+            path = tempfile.mktemp()
+        _Cleanup.append(path)
+        os.mkdir(path)
+        self.tempdir = path
+        self.orig_cwd = os.getcwd()
+        os.chdir(path)
+
+    def tearDown(self):
+        shutil.rmtree(self.tempdir)
+        _Cleanup.remove(self.tempdir)
+        os.chdir(self.orig_cwd)
+
+    def strip_initial_spaces(self, s):
+        #lines = s.split('\n')
+        lines = string.split(s, '\n')
+        spaces = re.match(' *', lines[0]).group(0)
+        def strip_spaces(l, spaces=spaces):
+            #if l.startswith(spaces):
+            if l[:len(spaces)] == spaces:
+                l = l[len(spaces):]
+            return l
+        #return '\n'.join([ strip_spaces(l) for l in lines ])
+        return string.join(map(strip_spaces, lines), '\n')
+
+    def write(self, file, contents):
+        open(file, 'w').write(self.strip_initial_spaces(contents))
+
+    def test_basic(self):
+        """Test basic file inclusion"""
+        self.write('f1.h', """\
+        #include "f2.h"
+        """)
+        self.write('f2.h', """\
+        #include <f3.h>
+        """)
+        self.write('f3.h', """\
+        """)
+        p = cpp.DumbPreProcessor(current = os.curdir,
+                                 cpppath = [os.curdir])
+        result = p('f1.h')
+        assert result == ['f2.h', 'f3.h'], result
+
+    def test_current_file(self):
+        """Test use of the .current_file attribute"""
+        self.write('f1.h', """\
+        #include <f2.h>
+        """)
+        self.write('f2.h', """\
+        #include "f3.h"
+        """)
+        self.write('f3.h', """\
+        """)
+        class MyPreProcessor(cpp.DumbPreProcessor):
+            def __init__(self, *args, **kw):
+                apply(cpp.DumbPreProcessor.__init__, (self,) + args, kw)
+                self.files = []
+            def __call__(self, file):
+                self.files.append(file)
+                return cpp.DumbPreProcessor.__call__(self, file)
+            def scons_current_file(self, t):
+                r = cpp.DumbPreProcessor.scons_current_file(self, t)
+                self.files.append(self.current_file)
+                return r
+        p = MyPreProcessor(current = os.curdir, cpppath = [os.curdir])
+        result = p('f1.h')
+        assert result == ['f2.h', 'f3.h'], result
+        assert p.files == ['f1.h', 'f2.h', 'f3.h', 'f2.h', 'f1.h'], p.files
+
+
+
 if __name__ == '__main__':
     suite = unittest.TestSuite()
     tclasses = [ PreProcessorTestCase,
                  DumbPreProcessorTestCase,
+                 fileTestCase,
                ]
     for tclass in tclasses:
         names = unittest.getTestCaseNames(tclass, 'test_')
+        try:
+            names = list(set(names))
+        except NameError:
+            pass
+        names.sort()
         suite.addTests(map(tclass, names))
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
         sys.exit(1)
-
index 284443a4a48d9f695f035ee8258aa62caf058534..d651ba92de2c7cd8a685e7aa9fd605fc1182447e 100644 (file)
@@ -38,6 +38,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import getopt
 import glob
 import os
+import os.path
 import re
 import shutil
 import string
@@ -62,6 +63,11 @@ except NameError:
 def make_temp_file(**kw):
     try:
         result = tempfile.mktemp(**kw)
+        try:
+            result = os.path.realpath(result)
+        except AttributeError:
+            # Python 2.1 has no os.path.realpath() method.
+            pass
     except TypeError:
         try:
             save_template = tempfile.template
@@ -122,7 +128,12 @@ class Line:
         if self.comment:
             print '# %s' % self.comment
         for x, y in self.points:
-            print fmt % (x, y)
+            # If y is None, it usually represents some kind of break
+            # in the line's index number.  We might want to represent
+            # this some way rather than just drawing the line straight
+            # between the two points on either side.
+            if not y is None:
+                print fmt % (x, y)
         print 'e'
 
     def get_x_values(self):
@@ -148,47 +159,59 @@ class Gnuplotter(Plotter):
 
     def vertical_bar(self, x, type, label, comment):
         if self.get_min_x() <= x and x <= self.get_max_x():
-            points = [(x, 0), (x, self.get_max_x())]
+            points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
             self.line(points, type, label, comment)
 
     def get_all_x_values(self):
         result = []
         for line in self.lines:
             result.extend(line.get_x_values())
-        return result
+        return filter(None, result)
 
     def get_all_y_values(self):
         result = []
         for line in self.lines:
             result.extend(line.get_y_values())
-        return result
+        return filter(None, result)
 
     def get_min_x(self):
         try:
             return self.min_x
         except AttributeError:
-            self.min_x = min(self.get_all_x_values())
+            try:
+                self.min_x = min(self.get_all_x_values())
+            except ValueError:
+                self.min_x = 0
             return self.min_x
 
     def get_max_x(self):
         try:
             return self.max_x
         except AttributeError:
-            self.max_x = max(self.get_all_x_values())
+            try:
+                self.max_x = max(self.get_all_x_values())
+            except ValueError:
+                self.max_x = 0
             return self.max_x
 
     def get_min_y(self):
         try:
             return self.min_y
         except AttributeError:
-            self.min_y = min(self.get_all_y_values())
+            try:
+                self.min_y = min(self.get_all_y_values())
+            except ValueError:
+                self.min_y = 0
             return self.min_y
 
     def get_max_y(self):
         try:
             return self.max_y
         except AttributeError:
-            self.max_y = max(self.get_all_y_values())
+            try:
+                self.max_y = max(self.get_all_y_values())
+            except ValueError:
+                self.max_y = 0
             return self.max_y
 
     def draw(self):
@@ -200,10 +223,17 @@ class Gnuplotter(Plotter):
             print 'set title "%s"' % self.title
         print 'set key %s' % self.key_location
 
+        min_y = self.get_min_y()
+        max_y = self.max_graph_value(self.get_max_y())
+        range = max_y - min_y
+        incr = range / 10.0
+        start = min_y + (max_y / 2.0) + (2.0 * incr)
+        position = [ start - (i * incr) for i in xrange(5) ]
+
         inx = 1
-        max_y = self.max_graph_value(self.get_max_y())/2
         for line in self.lines:
-            line.print_label(inx, line.points[0][0]-1, max_y)
+            line.print_label(inx, line.points[0][0]-1,
+                             position[(inx-1) % len(position)])
             inx += 1
 
         plot_strings = [ self.plot_string(l) for l in self.lines ]
@@ -485,6 +515,8 @@ class SConsTimer:
 
         for file in files:
             t = line_function(file, *args, **kw)
+            if t is None:
+                t = []
             diff = len(columns) - len(t)
             if diff > 0:
                 t += [''] * diff
@@ -564,7 +596,7 @@ class SConsTimer:
             except ValueError:
                 x, type, label = bar_tuple
                 comment = label
-            gp.vertical_bar(x, type, None, label, comment)
+            gp.vertical_bar(x, type, label, comment)
 
         gp.draw()
 
@@ -613,10 +645,17 @@ class SConsTimer:
         else:
             search_string = time_string
         contents = open(file).read()
+        if not contents:
+            sys.stderr.write('file %s has no contents!\n' % repr(file))
+            return None
         result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
         result = [ float(r) for r in result ]
         if not time_string is None:
-            result = result[0]
+            try:
+                result = result[0]
+            except IndexError:
+                sys.stderr.write('file %s has no results!\n' % repr(file))
+                return None
         return result
 
     def get_function_profile(self, file, function):
@@ -1294,7 +1333,12 @@ class SConsTimer:
                 commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
             else:
                 suffix = self.archive_splitext(archive)[1]
-                commands.append(self.unpack_map[suffix] + (archive,))
+                unpack_command = self.unpack_map.get(suffix)
+                if not unpack_command:
+                    dest = os.path.split(archive)[1]
+                    commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
+                else:
+                    commands.append(unpack_command + (archive,))
 
         commands.extend([
             (os.chdir, 'cd %%s', self.subdir),
index 0d6e6ac6082e47fc0797938067e35a925ee5edce..c446cad0a7a71688d2e662ba971fe43df39190e2 100644 (file)
@@ -127,6 +127,7 @@ check_list = [
             'engine/SCons/compat/_scons_optparse.py',
             'engine/SCons/compat/_scons_sets.py',
             'engine/SCons/compat/_scons_sets15.py',
+            'engine/SCons/compat/_scons_shlex.py',
             'engine/SCons/compat/_scons_subprocess.py',
             'engine/SCons/compat/_scons_textwrap.py',
             'engine/SCons/Conftest.py',
@@ -151,6 +152,7 @@ check_list = [
             'engine/SCons/compat/_scons_optparse.py',
             'engine/SCons/compat/_scons_sets.py',
             'engine/SCons/compat/_scons_sets15.py',
+            'engine/SCons/compat/_scons_shlex.py',
             'engine/SCons/compat/_scons_subprocess.py',
             'engine/SCons/compat/_scons_textwrap.py',
             'engine/SCons/Conftest.py',
@@ -171,6 +173,7 @@ check_list = [
             'SCons/compat/_scons_optparse.py',
             'SCons/compat/_scons_sets.py',
             'SCons/compat/_scons_sets15.py',
+            'SCons/compat/_scons_shlex.py',
             'SCons/compat/_scons_subprocess.py',
             'SCons/compat/_scons_textwrap.py',
             'SCons/Conftest.py',
@@ -214,6 +217,7 @@ check_list = [
             'src/engine/SCons/compat/_scons_optparse.py',
             'src/engine/SCons/compat/_scons_sets.py',
             'src/engine/SCons/compat/_scons_sets15.py',
+            'src/engine/SCons/compat/_scons_shlex.py',
             'src/engine/SCons/compat/_scons_subprocess.py',
             'src/engine/SCons/compat/_scons_textwrap.py',
             'src/engine/SCons/Conftest.py',
diff --git a/test/Actions/function.py b/test/Actions/function.py
new file mode 100644 (file)
index 0000000..00aa688
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+#
+# Test that the signature of function action includes all the
+# necessary pieces.
+# 
+
+test.write('SConstruct', r"""
+import re
+
+import SCons.Action
+import SCons.Builder
+
+options = Options()
+options.AddOptions(
+    ('header', 'Header string (default cell argument)', 'Head:'),
+    ('trailer', 'Trailer string (default cell argument)', 'Tail'),
+    ('NbDeps', 'Number of dependencies', '2'),
+    ('separator', 'Separator for the dependencies (function constant)', ':'),
+    ('closure_cell_value', 'Value of a closure cell', '25'),
+    ('b', 'Value of b (value default argument', '7'),
+    ('regexp', 'Regexp (object as a default argument', 'a(a*)'),
+    ('docstring', 'Docstring', 'docstring'),
+    ('extracode', 'Extra code for the builder function', ''),
+    ('extraarg', 'Extra arg builder function', ''),
+    ('nestedfuncexp', 'Expression for the nested function', 'xxx - b'),
+)
+
+optEnv = Environment(options=options, tools=[])
+
+r = re.compile(optEnv['regexp'])
+
+toto = \
+r'''
+def toto(header='%(header)s', trailer='%(trailer)s'):
+    xxx = %(closure_cell_value)s
+    def writeDeps(target, source, env, b=%(b)s, r=r %(extraarg)s ,
+                  header=header, trailer=trailer, xxx=xxx):
+        """+'"""%(docstring)s"""'+"""
+        def foo(b=b, xxx=xxx):
+            return %(nestedfuncexp)s
+        f = open(str(target[0]),'wb')
+        f.write(header)
+        for d in env['ENVDEPS']:
+            f.write(d+'%(separator)s')
+        f.write(trailer+'\\n')
+        f.write(str(foo())+'\\n')
+        f.write(r.match('aaaa').group(1)+'\\n')
+        %(extracode)s
+        try:
+           f.write(str(xarg)+'\\n')
+        except NameError:
+           pass
+        f.close()
+
+    return writeDeps
+'''
+
+exec( toto % optEnv )
+
+genHeaderBld = SCons.Builder.Builder(
+    action = SCons.Action.Action(
+        toto(), 
+        'Generating $TARGET',
+        varlist=['ENVDEPS']
+        ),
+    suffix = '.gen.h'
+    )
+
+env = Environment()
+env.Append(BUILDERS = {'GenHeader' : genHeaderBld})
+
+envdeps = map(str, range(int(optEnv['NbDeps'])))
+
+env.GenHeader('Out', None, ENVDEPS=envdeps)
+""")
+
+
+rebuildstr = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+Generating Out.gen.h
+scons: done building targets.
+"""
+
+nobuildstr = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: `.' is up to date.
+scons: done building targets.
+"""
+
+def runtest( arguments, expectedOutFile, expectedRebuild=True):
+    test.run(arguments=arguments,
+             stdout=expectedRebuild and rebuildstr or nobuildstr)
+    test.must_match('Out.gen.h', expectedOutFile)
+
+    # Should not be rebuild when ran a second time with the same
+    # arguments.
+
+    test.run(arguments = arguments, stdout=nobuildstr)
+    test.must_match('Out.gen.h', expectedOutFile)
+
+
+# Original build. 
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing a docstring should not cause a rebuild
+runtest('docstring=ThisBuilderDoesXAndY', """Head:0:1:Tail\n18\naaa\n""", False)
+runtest('docstring=SuperBuilder', """Head:0:1:Tail\n18\naaa\n""", False)
+runtest('docstring=', """Head:0:1:Tail\n18\naaa\n""", False)
+
+# Changing a variable listed in the varlist should cause a rebuild
+runtest('NbDeps=3', """Head:0:1:2:Tail\n18\naaa\n""")
+runtest('NbDeps=4', """Head:0:1:2:3:Tail\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the function code should cause a rebuild
+runtest('extracode=f.write("XX\\n")', """Head:0:1:Tail\n18\naaa\nXX\n""")
+runtest('extracode=a=2', """Head:0:1:Tail\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing a constant used in the function code should cause a rebuild
+runtest('separator=,', """Head:0,1,Tail\n18\naaa\n""")
+runtest('separator=;', """Head:0;1;Tail\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the code of a nested function should cause a rebuild
+runtest('nestedfuncexp=b-xxx', """Head:0:1:Tail\n-18\naaa\n""")
+runtest('nestedfuncexp=b+xxx', """Head:0:1:Tail\n32\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Adding an extra argument should cause a rebuild.
+runtest('extraarg=,xarg=2', """Head:0:1:Tail\n18\naaa\n2\n""")
+runtest('extraarg=,xarg=5', """Head:0:1:Tail\n18\naaa\n5\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the value of a default argument should cause a rebuild
+# case 1: a value
+runtest('b=0', """Head:0:1:Tail\n25\naaa\n""")
+runtest('b=9', """Head:0:1:Tail\n16\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# case 2: an object
+runtest('regexp=(aaaa)', """Head:0:1:Tail\n18\naaaa\n""")
+runtest('regexp=aa(a+)', """Head:0:1:Tail\n18\naa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the value of a closure cell value should cause a rebuild
+# case 1: a value
+runtest('closure_cell_value=32', """Head:0:1:Tail\n25\naaa\n""")
+runtest('closure_cell_value=7', """Head:0:1:Tail\n0\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# case 2: a default argument
+runtest('header=MyHeader:', """MyHeader:0:1:Tail\n18\naaa\n""")
+runtest('trailer=MyTrailer', """Head:0:1:MyTrailer\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+test.pass_test()
diff --git a/test/CPPDEFINES/basic.py b/test/CPPDEFINES/basic.py
new file mode 100644 (file)
index 0000000..5c302c3
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic use of CPPPDEFINES with various data types.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+test_list = [
+    'xyz',
+    ['x', 'y', 'z'],
+    ['x', ['y', 123], 'z', ('int', '$INTEGER')],
+    { 'c' : 3, 'b': None, 'a' : 1 },
+]
+env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0)
+for i in test_list:
+    print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
+env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1)
+for i in test_list:
+    print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
+""")
+
+expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n", 
+                          read_str = """\
+-Dxyz
+-Dx -Dy -Dz
+-Dx -Dy=123 -Dz -Dint=0
+-Da=1 -Db -Dc=3
+|xyz|
+|x| |y| |z|
+|x| |y=123| |z| |int=1|
+|a=1| |b| |c=3|
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+test.pass_test()
similarity index 67%
rename from test/CPPDEFINES.py
rename to test/CPPDEFINES/live.py
index c38f857b2bcf6be198907328d2ed2e069d5b1db8..7a169e8b2f5ce8d3890cdb6d7a723f87701d2436 100644 (file)
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 """
-XXX Put a description of the test here.
+Verify basic use of CPPDEFINES with live compilation.
 """
 
-import string
-
 import TestSCons
 
 test = TestSCons.TestSCons()
 
-# Make sure $_CPPDEFFLAGS doesn't barf when CPPDEFINES isn't defined.
-test.write('SConstruct', """\
-env = Environment()
-print env.subst('$_CPPDEFFLAGS')
-""")
-
-expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n", 
-                          read_str = "\n")
-
-test.run(arguments = '.', stdout=expect)
-
-# Test CPPDEFINES as a string and a list.
-test.write('SConstruct', """\
-test_list = [
-    'xyz',
-    ['x', 'y', 'z'],
-    ['x', ['y', 123], 'z', ('int', '$INTEGER')],
-    { 'c' : 3, 'b': None, 'a' : 1 },
-]
-env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0)
-for i in test_list:
-    print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
-env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1)
-for i in test_list:
-    print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
-""")
-
-expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n", 
-                          read_str = """\
--Dxyz
--Dx -Dy -Dz
--Dx -Dy=123 -Dz -Dint=0
--Da=1 -Db -Dc=3
-|xyz|
-|x| |y| |z|
-|x| |y=123| |z| |int=1|
-|a=1| |b| |c=3|
-""")
-
-test.run(arguments = '.', stdout=expect)
-
 test.write('SConstruct', """\
 foo = Environment(CPPDEFINES = ['FOO', ('VAL', '$VALUE')], VALUE=7)
 bar = Environment(CPPDEFINES = {'BAR':None, 'VAL':8})
diff --git a/test/CPPDEFINES/scan.py b/test/CPPDEFINES/scan.py
new file mode 100644 (file)
index 0000000..c9b60c3
--- /dev/null
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of the Scanner that evaluates CPP lines works as expected.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+m = 'Scanner evaluation of CPP lines not yet supported; skipping test.\n'
+test.skip_test(m)
+
+f1_exe = 'f1' + TestSCons._exe
+f2_exe = 'f2' + TestSCons._exe
+f3_exe = 'f3' + TestSCons._exe
+f4_exe = 'f4' + TestSCons._exe
+
+test.write('SConstruct', """\
+env = Environment(CPPPATH = ['.'])
+
+f1 = env.Object('f1', 'fff.c', CPPDEFINES = ['F1'])
+f2 = env.Object('f2', 'fff.c', CPPDEFINES = [('F2', 1)])
+f3 = env.Object('f3', 'fff.c', CPPDEFINES = {'F3':None})
+f4 = env.Object('f4', 'fff.c', CPPDEFINES = {'F4':1})
+
+env.Program('f1', ['prog.c', f1])
+env.Program('f2', ['prog.c', f2])
+env.Program('f3', ['prog.c', f3])
+env.Program('f4', ['prog.c', f4])
+""")
+
+test.write('f1.h', """
+#define STRING  "F1"
+""")
+
+test.write('f2.h', """
+#define STRING  "F2"
+""")
+
+test.write('f3.h', """
+#define STRING  "F3"
+""")
+
+test.write('f4.h', """
+#define STRING  "F4"
+""")
+
+test.write('fff.c', """
+#ifdef  F1
+#include <f1.h>
+#endif
+#if     F2
+#include <f2.h>
+#endif
+#ifdef  F3
+#include <f3.h>
+#endif
+#ifdef  F4
+#include <f4.h>
+#endif
+
+char *
+foo(void)
+{
+    return (STRING);
+}
+""")
+
+
+test.write('prog.c', r"""
+#include <stdio.h>
+#include <stdlib.h>
+
+extern char *foo(void);
+
+int
+main(int argc, char *argv[])
+{
+        argv[argc++] = "--";
+        printf("prog.c:  %s\n", foo());
+        exit (0);
+}
+""")
+
+
+
+test.run(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c:  F1\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c:  F2\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c:  F3\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c:  F4\n")
+
+
+
+test.write('f1.h', """
+#define STRING  "F1 again"
+""")
+
+test.up_to_date(arguments = '%(f2_exe)s %(f3_exe)s %(f4_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c:  F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c:  F2\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c:  F3\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c:  F4\n")
+
+
+
+test.write('f2.h', """
+#define STRING  "F2 again"
+""")
+
+test.up_to_date(arguments = '%(f1_exe)s %(f3_exe)s %(f4_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c:  F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c:  F2 again\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c:  F3\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c:  F4\n")
+
+
+
+test.write('f3.h', """
+#define STRING  "F3 again"
+""")
+
+test.up_to_date(arguments = '%(f1_exe)s %(f2_exe)s %(f4_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c:  F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c:  F2 again\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c:  F3 again\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c:  F4\n")
+
+
+
+test.write('f4.h', """
+#define STRING  "F4 again"
+""")
+
+test.up_to_date(arguments = '%(f1_exe)s %(f2_exe)s %(f3_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c:  F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c:  F2 again\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c:  F3 again\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c:  F4 again\n")
+
+
+
+test.pass_test()
diff --git a/test/CPPDEFINES/undefined.py b/test/CPPDEFINES/undefined.py
new file mode 100644 (file)
index 0000000..b6b8b44
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that $_CPPDEFFLAGS doesn't barf when CPPDEFINES isn't defined.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+print env.subst('$_CPPDEFFLAGS')
+""")
+
+expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n", 
+                          read_str = "\n")
+
+test.run(arguments = '.', stdout=expect)
+
+test.pass_test()
diff --git a/test/CPPPATH/absolute-path.py b/test/CPPPATH/absolute-path.py
new file mode 100644 (file)
index 0000000..9adb206
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability to #include a file with an absolute path name.  (Which
+is not strictly a test of using $CPPPATH, but it's in the ball park...)
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('include', 'work')
+
+inc_h = test.workpath('include', 'inc.h')
+does_not_exist_h = test.workpath('include', 'does_not_exist.h')
+
+test.write(['work', 'SConstruct'], """\
+Program('prog.c')
+""")
+
+test.write(['work', 'prog.c'], """\
+#include <stdio.h>
+#include "%(inc_h)s"
+#if     0
+#include "%(does_not_exist_h)s"
+#endif
+
+int
+main(int argc, char *argv[])
+{
+    argv[argc++] = "--";
+    printf("%%s\\n", STRING);
+    return 0;
+}
+""" % locals())
+
+test.write(['include', 'inc.h'], """\
+#define STRING  "include/inc.h 1\\n"
+""")
+
+test.run(chdir = 'work', arguments = '.')
+
+test.up_to_date(chdir = 'work', arguments = '.')
+
+test.write(['include', 'inc.h'], """\
+#define STRING  "include/inc.h 2\\n"
+""")
+
+test.not_up_to_date(chdir = 'work', arguments = '.')
+
+test.pass_test()
diff --git a/test/CPPPATH/function-expansion.py b/test/CPPPATH/function-expansion.py
new file mode 100644 (file)
index 0000000..7c80f22
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that expansion of construction variables whose values are functions
+(that return lists that contain Nodes, even) works as expected within
+a $CPPPATH list definition.
+
+This used to cause TypeErrors when the _concat expansion tried to
+join the Nodes with the strings.
+
+Test courtesy Konstantin Bozhikov.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('list_inc1',
+            'list_inc2',
+            'inc1',
+            'inc2',
+            'inc3',
+            ['inc3', 'subdir'])
+
+test.write('SConstruct', """\
+env = Environment()
+def my_cpppaths( target, source, env, for_signature ):
+    return [ Dir('list_inc1'), Dir('list_inc2') ]
+
+env = Environment( CPPPATH = [Dir('inc1'), '$INC2', '$INC3/subdir', '$MY_CPPPATHS' ],
+                   INC2 = Dir('inc2'),
+                   INC3 = Dir('inc3'),
+                   MY_CPPPATHS = my_cpppaths )
+
+env.Program('prog.c')
+""")
+
+test.write('prog.c', """\
+#include <stdio.h>
+#include <stdlib.h>
+#include "string_list_1.h"
+#include "string_list_2.h"
+#include "string_1.h"
+#include "string_2.h"
+#include "string_3.h"
+
+int
+main(int argc, char *argv[])
+{
+        argv[argc++] = "--";
+        printf("prog.c\\n");
+        printf("%s\\n", STRING_LIST_1);
+        printf("%s\\n", STRING_LIST_2);
+        printf("%s\\n", STRING_1);
+        printf("%s\\n", STRING_2);
+        printf("%s\\n", STRING_3);
+        exit (0);
+}
+""")
+
+test.write(['list_inc1', 'string_list_1.h'], """\
+#define STRING_LIST_1   "list_inc1/string_list_1.h"
+""")
+
+test.write(['list_inc2', 'string_list_2.h'], """\
+#define STRING_LIST_2   "list_inc2/string_list_2.h"
+""")
+
+test.write(['inc1', 'string_1.h'], """\
+#define STRING_1        "inc1/string_1.h"
+""")
+
+test.write(['inc2', 'string_2.h'], """\
+#define STRING_2        "inc2/string_2.h"
+""")
+
+test.write(['inc3', 'subdir', 'string_3.h'], """\
+#define STRING_3        "inc3/subdir/string_3.h"
+""")
+
+test.run()
+
+test.up_to_date(arguments = '.')
+
+expect = """\
+prog.c
+list_inc1/string_list_1.h
+list_inc2/string_list_2.h
+inc1/string_1.h
+inc2/string_2.h
+inc3/subdir/string_3.h
+"""
+
+test.run(program = test.workpath('prog' + TestSCons._exe), stdout=expect)
+
+test.write(['inc3', 'subdir', 'string_3.h'], """\
+#define STRING_3        "inc3/subdir/string_3.h 2"
+""")
+
+test.not_up_to_date(arguments = '.')
+
+expect = """\
+prog.c
+list_inc1/string_list_1.h
+list_inc2/string_list_2.h
+inc1/string_1.h
+inc2/string_2.h
+inc3/subdir/string_3.h 2
+"""
+
+test.run(program = test.workpath('prog' + TestSCons._exe), stdout=expect)
+
+test.pass_test()
diff --git a/test/CPPPATH/list-expansion.py b/test/CPPPATH/list-expansion.py
new file mode 100644 (file)
index 0000000..7e5326f
--- /dev/null
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that expansion of construction variables whose values are
+lists works as expected within a $CPPPATH list definition.
+
+Previously, the stringification of the expansion of the individual
+variables would turn a list like ['sub1', 'sub2'] below into "-Isub1 sub2"
+on the command line.
+
+Test case courtesy Daniel Svensson.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('sub1', 'sub2', 'sub3', 'sub4')
+
+test.write('SConstruct', """\
+class _inc_test:
+    def __init__(self, name):
+        self.name = name
+
+    def __call__(self, target, source, env, for_signature):
+        return env.something[self.name]
+
+env = Environment()
+
+env.something = {}
+env.something['test'] = ['sub1', 'sub2']
+
+env['INC_PATHS1'] = _inc_test
+
+env['INC_PATHS2'] = ['sub3', 'sub4']
+
+env.Append(CPPPATH = ['${INC_PATHS1("test")}', '$INC_PATHS2'])
+env.Program('test', 'test.c')
+""")
+
+test.write('test.c', """\
+#include <stdio.h>
+#include <stdlib.h>
+#include "string1.h"
+#include "string2.h"
+#include "string3.h"
+#include "string4.h"
+
+int
+main(int argc, char *argv[])
+{
+        argv[argc++] = "--";
+        printf("test.c\\n");
+        printf("%s\\n", STRING1);
+        printf("%s\\n", STRING2);
+        printf("%s\\n", STRING3);
+        printf("%s\\n", STRING4);
+        exit (0);
+}
+""")
+
+test.write(['sub1', 'string1.h'], """\
+#define STRING1 "sub1/string1.h"
+""")
+
+test.write(['sub2', 'string2.h'], """\
+#define STRING2 "sub2/string2.h"
+""")
+
+test.write(['sub3', 'string3.h'], """\
+#define STRING3 "sub3/string3.h"
+""")
+
+test.write(['sub4', 'string4.h'], """\
+#define STRING4 "sub4/string4.h"
+""")
+
+test.run()
+
+test.up_to_date(arguments = '.')
+
+expect = """\
+test.c
+sub1/string1.h
+sub2/string2.h
+sub3/string3.h
+sub4/string4.h
+"""
+
+test.run(program = test.workpath('test' + TestSCons._exe), stdout=expect)
+
+test.write(['sub2', 'string2.h'], """\
+#define STRING2 "sub2/string2.h 2"
+""")
+
+test.not_up_to_date(arguments = '.')
+
+expect = """\
+test.c
+sub1/string1.h
+sub2/string2.h 2
+sub3/string3.h
+sub4/string4.h
+"""
+
+test.run(program = test.workpath('test' + TestSCons._exe), stdout=expect)
+
+test.pass_test()
index a6f2fa5cecc7ae0392042f02529382f105f003fc..1ce114c67829a9c91a142f34e41798a0a1ba034c 100644 (file)
@@ -35,7 +35,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-test.write('cmd.py', r"""
+test.write('mycommand.py', r"""
 import sys
 sys.stderr.write( 'Hello World on stderr\n' )
 sys.stdout.write( 'Hello World on stdout\n' )
@@ -48,7 +48,7 @@ def CustomTest(*args):
     return 0
 conf = env.Configure(custom_tests = {'MyTest' : CustomTest})
 if not conf.MyTest():
-    env.Command("hello", [], '%(_python_)s cmd.py $TARGET')
+    env.Command("hello", [], '%(_python_)s mycommand.py $TARGET')
 env = conf.Finish()
 """ % locals())
 
similarity index 100%
rename from test/DMD.py
rename to test/D/DMD.py
diff --git a/test/D/Scanner.py b/test/D/Scanner.py
new file mode 100644 (file)
index 0000000..5c69820
--- /dev/null
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the D scanner can return multiple modules imported by
+a single statement.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+_obj = TestSCons._obj
+
+dmd = test.where_is('dmd')
+if not dmd:
+    test.skip_test("Could not find 'dmd'; skipping test.\n")
+
+test.subdir(['p'])
+
+test.write('SConstruct', """
+env = Environment()
+env.Program('test1.d')
+env.Program('test2.d')
+""")
+
+test.write(['test1.d'], """\
+import module1;
+import module2;
+import module3;
+import p.submodule1;
+import p.submodule2;
+
+int main() {
+   return 0;
+}
+""")
+
+test.write(['test2.d'], """\
+import
+   module1,
+   module2,
+   module3;
+import
+   p.submodule1,
+   p.submodule2;
+
+int main() {
+   return 0;
+}
+""")
+
+test.write(['ignored.d'], """\
+module  ignored;
+
+int something;
+""")
+
+test.write(['module1.d'], """\
+module  module1;
+
+int something;
+""")
+
+test.write(['module2.d'], """\
+module  module2;
+
+int something;
+""")
+
+test.write(['module3.di'], """\
+module  module3;
+
+int something;
+""")
+
+test.write(['p', 'ignored.d'], """\
+module  p.ignored;
+
+int something;
+""")
+
+test.write(['p', 'submodule1.d'], """\
+module  p.submodule1;
+
+int something;
+""")
+
+test.write(['p', 'submodule2.d'], """\
+module p.submodule2;
+
+int something;
+""")
+
+arguments = 'test1%(_obj)s test2%(_obj)s' % locals()
+
+test.run(arguments = arguments)
+
+test.up_to_date(arguments = arguments)
+
+test.write(['module2.d'], """\
+module  module2;
+
+int something_else;
+""")
+
+test.not_up_to_date(arguments = arguments)
+
+test.up_to_date(arguments = arguments)
+
+test.write(['p', 'submodule2.d'], """\
+module p.submodule2;
+
+int something_else;
+""")
+
+test.not_up_to_date(arguments = arguments)
+
+test.up_to_date(arguments = arguments)
+
+test.pass_test()
index b9ff1ffda32f3ec6c34237537ae4549dc33bde3f..956caa7de00e3432fee8d051863a4b64a44dc7df 100644 (file)
@@ -37,8 +37,10 @@ test.write('SConstruct', """
 a ! x
 """)
 
+# It looks like vanilla Python 2.2 is the only version that
+# puts "<string>" here in place of the file name.
 test.run(stdout = "scons: Reading SConscript files ...\n",
-         stderr = """  File ".+SConstruct", line 2
+         stderr = """  File "(.+SConstruct|<string>)", line 2
 
     a ! x
 
diff --git a/test/Errors/execute-a-directory.py b/test/Errors/execute-a-directory.py
new file mode 100644 (file)
index 0000000..bcdcb7c
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+not_executable = test.workpath("not_executable")
+
+test.write(not_executable, "\n")
+
+test.write("f3.in", "\n")
+
+test.write('SConstruct', r"""
+bld = Builder(action = '%s $SOURCES $TARGET')
+env = Environment(BUILDERS = { 'bld' : bld })
+env.bld(target = 'f3', source = 'f3.in')
+""" % string.replace(test.workdir, '\\', '\\\\'))
+
+test.run(arguments='.',
+         stdout = test.wrap_stdout("%s f3.in f3\n" % test.workdir, error=1),
+         stderr = None,
+         status = 2)
+
+bad_command = """\
+Bad command or file name
+"""
+
+unrecognized = """\
+'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """\
+The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+cannot_execute = """\
+%s: cannot execute
+scons *** [%s] Error 126
+"""
+
+Permission_denied = """\
+%s: Permission denied
+scons: *** [%s] Error 126
+"""
+
+permission_denied = """\
+%s: permission denied
+scons: *** [%s] Error 126
+"""
+
+is_a_directory = """\
+%s: is a directory
+scons: *** [%s] Error 126
+"""
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+    errs = [
+        bad_command,
+        unrecognized % (test.workdir, 'f3'),
+        unspecified % 'f3'
+    ]
+    test.fail_test(not test.stderr() in errs)
+else:
+    errs = [
+        cannot_execute % (not_executable, 'f3'),
+        is_a_directory % (test.workdir, 'f3'),
+        Permission_denied % (test.workdir, 'f3'),
+        Permission_denied % (test.workdir, 'f3'),
+    ]
+    error_message_not_found = 1
+    for err in errs:
+        if string.find(test.stderr(), err) != -1:
+            error_message_not_found = None
+            break
+    test.fail_test(error_message_not_found)
+
+test.pass_test()
diff --git a/test/Errors/non-executable-file.py b/test/Errors/non-executable-file.py
new file mode 100644 (file)
index 0000000..d6e018b
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+not_executable = test.workpath("not_executable")
+
+test.write(not_executable, "\n")
+
+test.write("f1.in", "\n")
+
+bad_command = """\
+Bad command or file name
+"""
+
+unrecognized = """\
+'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """\
+The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+cannot_execute = """\
+%s: cannot execute
+scons *** [%s] Error 126
+"""
+
+Permission_denied = """\
+%s: Permission denied
+scons: *** [%s] Error 126
+"""
+
+permission_denied = """\
+%s: permission denied
+scons: *** [%s] Error 126
+"""
+
+test.write('SConstruct', r"""
+bld = Builder(action = '%s $SOURCES $TARGET')
+env = Environment(BUILDERS = { 'bld': bld })
+env.bld(target = 'f1', source = 'f1.in')
+""" % string.replace(not_executable, '\\', '\\\\'))
+
+test.run(arguments='.',
+         stdout = test.wrap_stdout("%s f1.in f1\n" % not_executable, error=1),
+         stderr = None,
+         status = 2)
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+    errs = [
+        bad_command,
+        unrecognized % (not_executable, 'f1'),
+        unspecified % 'f1'
+    ]
+    test.fail_test(not test.stderr() in errs)
+else:
+    errs = [
+        cannot_execute % (not_executable, 'f1'),
+        Permission_denied % (not_executable, 'f1'),
+        permission_denied % (not_executable, 'f1'),
+    ]
+    error_message_not_found = 1
+    for err in errs:
+        if string.find(test.stderr(), err) != -1:
+            error_message_not_found = None
+            break
+    test.fail_test(error_message_not_found)
+
+test.pass_test()
diff --git a/test/Errors/nonexistent-executable.py b/test/Errors/nonexistent-executable.py
new file mode 100644 (file)
index 0000000..b2a9557
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+no_such_file = test.workpath("no_such_file")
+
+test.write("f1.in", "\n")
+
+test.write('SConstruct', r"""
+bld = Builder(action = '%s $SOURCES $TARGET')
+env = Environment(BUILDERS = { 'bld' : bld })
+env.bld(target = 'f1', source = 'f1.in')
+""" % string.replace(no_such_file, '\\', '\\\\'))
+
+test.run(arguments='.',
+         stdout = test.wrap_stdout("%s f1.in f1\n" % no_such_file, error=1),
+         stderr = None,
+         status = 2)
+
+bad_command = """\
+Bad command or file name
+"""
+
+unrecognized = """\
+'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """\
+The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+not_found_1 = """
+sh: %s: not found
+scons: *** [%s] Error 1
+"""
+
+not_found_2 = """
+sh: %s:  not found
+scons: *** [%s] Error 1
+"""
+
+not_found_127 = """\
+sh: %s: not found
+scons: *** [%s] Error 127
+"""
+
+No_such = """\
+%s: No such file or directory
+scons: *** [%s] Error 127
+"""
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+    errs = [
+        bad_command,
+        unrecognized % (no_such_file, 'f1'),
+        unspecified % 'f1'
+    ]
+    test.fail_test(not test.stderr() in errs)
+else:
+    errs = [
+        not_found_1 % (no_such_file, 'f1'),
+        not_found_2 % (no_such_file, 'f1'),
+        not_found_127 % (no_such_file, 'f1'),
+        No_such % (no_such_file, 'f1'),
+    ]
+    error_message_not_found = 1
+    for err in errs:
+        if string.find(test.stderr(), err) != -1:
+            error_message_not_found = None
+            break
+    test.fail_test(error_message_not_found)
+
+test.pass_test()
diff --git a/test/Errors/permission-denied.py b/test/Errors/permission-denied.py
new file mode 100644 (file)
index 0000000..05d1e9d
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', r"""
+env = Environment()
+env.Command('test.out', 'test.in', Copy('$TARGET', '$SOURCE'))
+env.InstallAs('test2.out', 'test.out')
+# Mark test2.out as precious so we'll handle the exception in
+# FunctionAction() rather than when the target is cleaned before building.
+env.Precious('test2.out')
+env.Default('test2.out')
+""")
+
+test.write('test.in', "test.in 1\n")
+
+test.run(arguments = '.')
+
+test.write('test.in', "test.in 2\n")
+
+test.writable('test2.out', 0)
+f = open(test.workpath('test2.out'))
+
+test.run(arguments = '.',
+         stderr = None,
+         status = 2)
+
+f.close()
+test.writable('test2.out', 1)
+
+test.description_set("Incorrect STDERR:\n%s" % test.stderr())
+errs = [
+    "scons: *** [test2.out] test2.out: Permission denied\n",
+    "scons: *** [test2.out] test2.out: permission denied\n",
+]
+test.fail_test(test.stderr() not in errs)
+
+test.pass_test()
index 4cf9310fc19005946a3c9dcefce8cb762b6992f5..38a7915c417571a5905b45d58d3ba49ec52c2d39 100644 (file)
@@ -48,9 +48,10 @@ file1_out       = target+os.path.join( target, destdir, 'file1.out' )
 #
 test.write('SConstruct', r"""
 env = Environment(SUBDIR='subdir')
-env.Install(r'%(destdir)s', 'file1.out')
-env.InstallAs(['file2.out', r'%(_SUBDIR_file3_out)s'],
-              ['file2.in', r'%(_SUBDIR_file3_in)s'])
+f1 = env.Install(r'%(destdir)s', 'file1.out')
+f2 = env.InstallAs(['file2.out', r'%(_SUBDIR_file3_out)s'],
+                   ['file2.in', r'%(_SUBDIR_file3_in)s'])
+env.Depends(f2, f1)
 """ % locals())
 
 test.write('file1.out', "file1.out\n")
@@ -58,9 +59,9 @@ test.write('file2.in', "file2.in\n")
 test.write(['subdir', 'file3.in'], "subdir/file3.in\n")
 
 expect = test.wrap_stdout("""\
+Install file: "file1.out" as "%(file1_out)s"
 Install file: "file2.in" as "%(target_file2_out)s"
 Install file: "%(subdir_file3_in)s" as "%(target_subdir_file3_out)s"
-Install file: "file1.out" as "%(file1_out)s"
 """ % locals())
 
 test.run(arguments = '--install-sandbox=%s' % destdir, stdout=expect)
diff --git a/test/Interactive/Alias.py b/test/Interactive/Alias.py
new file mode 100644 (file)
index 0000000..fc05b9a
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability to build an Alias in --interactive mode.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+foo = Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Alias('foo-alias', foo)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo-alias\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmyn for the "build" command.
+scons.send("scons foo-alias\n")
+
+scons.send("scons 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo-alias\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: `foo-alias' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/Default-None.py b/test/Interactive/Default-None.py
new file mode 100644 (file)
index 0000000..36ebf2f
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we get the expected error message when we use a "build"
+command without arguments and there are no default targets (because they
+explicitly called Default(None) in the SConstruct file).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons(combine=1)
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Default(None)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build\n")
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmyn for the "build" command.
+scons.send("scons\n")
+
+scons.send("scons foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build\n")
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> scons: *** No targets specified and no Default() targets found.  Stop.
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> scons: *** No targets specified and no Default() targets found.  Stop.
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: *** No targets specified and no Default() targets found.  Stop.
+scons>>> scons: `foo.out' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/Default.py b/test/Interactive/Default.py
new file mode 100644 (file)
index 0000000..d6205e2
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we can use a "build" command without arguments to (re-)build
+the Default() targets, without exiting the command loop.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+foo_out = Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Default(foo_out)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmy for the "build" command.
+scons.send("scons\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/added-include.py b/test/Interactive/added-include.py
new file mode 100644 (file)
index 0000000..8c30314
--- /dev/null
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This verifies the --interactive command line option's ability to
+rebuild a target when an implicit dependency (include line) is
+added to the source file.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('foo.h.in', """
+#define FOO_STRING "foo.h.in"
+""")
+
+test.write('foo.c', """
+#include <stdio.h>
+
+int main()
+{
+    printf("foo.c\\n");
+    return 0;
+}
+""")
+
+test.write('SConstruct', """
+env = Environment(CPPPATH=['.'])
+env.Command('foo.h', ['foo.h.in'], Copy('$TARGET', '$SOURCE'))
+env.Program('foo', ['foo.c'])
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+foo_exe     = test.workpath('foo' + TestSCons._exe)
+_foo_exe_   = '"%s"' % string.replace(foo_exe, '\\', '\\\\')
+
+
+
+# Start scons, to build "foo"
+scons = test.start(arguments = '--interactive')
+
+scons.send("build %(_foo_exe_)s 1\n" % locals())
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.run(program = foo_exe, stdout = 'foo.c\n')
+
+
+
+
+# Update foo.c
+# We add a new #include line, to make sure that scons notices
+# the new implicit dependency and builds foo.h first.
+test.write('foo.c', """
+#include <foo.h>
+
+#include <stdio.h>
+
+int main()
+{
+    printf("%s\\n", FOO_STRING);
+    return 0;
+}
+""")
+
+scons.send("build %(_foo_exe_)s 2\n" % locals())
+
+test.wait_for(test.workpath('2'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = foo_exe, stdout = 'foo.h.in\n')
+
+
+
+test.write('foo.h.in', """
+#define FOO_STRING "foo.h.in 3"
+""")
+
+
+
+scons.send("build %(_foo_exe_)s 3\n" % locals())
+
+test.wait_for(test.workpath('3'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = foo_exe, stdout = 'foo.h.in 3\n')
+
+
+
+test.pass_test()
diff --git a/test/Interactive/basic.py b/test/Interactive/basic.py
new file mode 100644 (file)
index 0000000..4c78f69
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the --interactive command line option to build
+a target, rebuild it when the input changes, and not rebuild it when
+the input doesn't change.
+
+Also tests that "scons" can be used as a synonym for "build".
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmy for the "build" command.
+scons.send("scons foo.out 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-debug.py b/test/Interactive/cache-debug.py
new file mode 100644 (file)
index 0000000..dab5159
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build a target when the
+--cache-debug option is used.
+"""
+
+import TestCmd
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+Command('5', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'), popen=scons)
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build --cache-debug=- foo.out\n")
+
+scons.send("build 5\n")
+
+test.wait_for(test.workpath('5'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = \
+r"""scons>>> Copy\("foo.out", "foo.in"\)
+scons>>> Touch\("1"\)
+scons>>> Removed foo.out
+scons>>> Touch\("2"\)
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch\("3"\)
+scons>>> Removed foo.out
+scons>>> Touch\("4"\)
+scons>>> Retrieved `foo.out' from cache
+CacheRetrieve\(foo.out\):  retrieving from [0-9A-za-z]+
+scons>>> Touch\("5"\)
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout, match=TestCmd.match_re)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-disable.py b/test/Interactive/cache-disable.py
new file mode 100644 (file)
index 0000000..0fb8435
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build
+a target when the --cache-disable option is used.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+Command('5', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build --cache-disable foo.out\n")
+
+scons.send("build 5\n")
+
+test.wait_for(test.workpath('5'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Removed foo.out
+scons>>> Touch("2")
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("3")
+scons>>> Removed foo.out
+scons>>> Touch("4")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("5")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-force.py b/test/Interactive/cache-force.py
new file mode 100644 (file)
index 0000000..8fd3f92
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build a target when the
+--cache-force option is used.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+import shutil
+shutil.rmtree(test.workpath('cache'))
+
+
+
+scons.send("build --cache-force foo.out\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Removed foo.out
+scons>>> Touch("2")
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("3")
+scons>>> scons: `foo.out' is up to date.
+scons>>> Removed foo.out
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("4")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-show.py b/test/Interactive/cache-show.py
new file mode 100644 (file)
index 0000000..c1fe487
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build a target when the
+--cache-show option is used.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+Command('5', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build --cache-show foo.out\n")
+
+scons.send("build 5\n")
+
+test.wait_for(test.workpath('5'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Removed foo.out
+scons>>> Touch("2")
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("3")
+scons>>> Removed foo.out
+scons>>> Touch("4")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("5")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/clean.py b/test/Interactive/clean.py
new file mode 100644 (file)
index 0000000..4f4f80d
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verifies operation of the --interactive command line option
+"clean" subcommand.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('f1.out', 'f1.in', Copy('$TARGET', '$SOURCE'))
+Command('f2.out', 'f2.in', Copy('$TARGET', '$SOURCE'))
+Command('f3.out', 'f3.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build f1.out f2.out f3.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('f1.out'), "f1.in\n")
+test.must_match(test.workpath('f2.out'), "f2.in\n")
+test.must_match(test.workpath('f3.out'), "f3.in\n")
+
+
+
+scons.send("clean f1.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_not_exist('f1.out')
+test.must_exist('f2.out')
+test.must_exist('f3.out')
+
+
+
+scons.send("build -c\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_not_exist('f1.out')
+test.must_not_exist('f2.out')
+test.must_not_exist('f3.out')
+
+expect_stdout = """\
+scons>>> Copy("f1.out", "f1.in")
+Copy("f2.out", "f2.in")
+Copy("f3.out", "f3.in")
+scons>>> Touch("1")
+scons>>> Removed f1.out
+scons>>> Touch("2")
+scons>>> Removed 1
+Removed 2
+Removed f2.out
+Removed f3.out
+scons>>> Touch("3")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/exit.py b/test/Interactive/exit.py
new file mode 100644 (file)
index 0000000..df06d5d
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify use of the "exit" subcommand.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('exit\n')
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> """
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/help.py b/test/Interactive/help.py
new file mode 100644 (file)
index 0000000..ef4c578
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the behavior of the "help" subcommand (and its "h" and "?" aliases).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('help\n')
+
+scons.send('h\n')
+
+scons.send('?\n')
+
+help_text = """\
+build [TARGETS]         Build the specified TARGETS and their dependencies.
+                        'b' is a synonym.
+clean [TARGETS]         Clean (remove) the specified TARGETS and their
+                        dependencies.  'c' is a synonym.
+exit                    Exit SCons interactive mode.
+help [COMMAND]          Prints help for the specified COMMAND.  'h' and
+                        '?' are synonyms.
+shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and '!'
+                        are synonyms.
+version                 Prints SCons version information.
+"""
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> %(help_text)s
+scons>>> %(help_text)s
+scons>>> %(help_text)s
+scons>>> 
+""" % locals()
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/implicit-BuildDir.py b/test/Interactive/implicit-BuildDir.py
new file mode 100644 (file)
index 0000000..7b7aa4b
--- /dev/null
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This is a regression test for a bug in earlier versions of the
+--interactive command line option (specifically the original prototype
+submitted by Adam Simpkins, who created this test case).
+
+It tests to make sure that cached state is cleared between files for
+nodes in both the build tree and the source tree when BuildDirs are used.
+This is needed especially with BuildDirs created with duplicate=0, since
+the scanners scan the files in the source tree.  Any cached implicit
+deps must be cleared on the source files.
+"""
+
+import os.path
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src',
+            ['src', 'inc'])
+
+# Create the top-level SConstruct file
+test.write('SConstruct', """
+BUILD_ENV = Environment()
+Export('BUILD_ENV')
+
+hdr_dir = '#build/include'
+BUILD_ENV['HDR_DIR'] = hdr_dir
+BUILD_ENV.Append(CPPPATH = hdr_dir)
+
+BUILD_ENV.BuildDir('build', 'src', duplicate = 0)
+SConscript('build/SConscript')
+
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+# Create the src/SConscript file
+test.write(['src', 'SConscript'], """
+Import('BUILD_ENV')
+BUILD_ENV.Install(BUILD_ENV['HDR_DIR'], ['inc/foo.h'])
+BUILD_ENV.Program('foo', ['foo.c'])
+""")
+
+# Create src/foo.c
+test.write(['src', 'foo.c'], """
+#include <stdio.h>
+
+#define FOO_PRINT_STRING "Hello from foo.c"
+
+int main()
+{
+    printf(FOO_PRINT_STRING "\\n");
+    return 0;
+}
+""")
+
+# Create src/inc/foo.h
+test.write(['src', 'inc', 'foo.h'], """
+#ifndef INCLUDED_foo_h
+#define INCLUDED_foo_h
+
+#define FOO_PRINT_STRING "Hello from foo.h"
+
+#endif /* INCLUDED_foo_h */
+""")
+
+# Start scons, to build only "build/foo"
+build_foo_exe   = os.path.join('build', 'foo' + TestSCons._exe)
+_build_foo_exe_ = '"%s"' % string.replace(build_foo_exe, '\\', '\\\\')
+abs_foo_exe     = test.workpath(build_foo_exe)
+
+scons = test.start(arguments = '--interactive', combine=1)
+
+
+
+# Build build/foo
+scons.send('build %(_build_foo_exe_)s 1\n' % locals())
+
+test.wait_for(test.workpath('1'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = abs_foo_exe, stdout = 'Hello from foo.c\n')
+
+
+
+# Update foo.c to include foo.h
+test.write(['src', 'foo.c'], """
+#include "foo.h"
+#include <stdio.h>
+
+int main()
+{
+    printf(FOO_PRINT_STRING "\\n");
+    return 0;
+}
+""")
+
+# Build build/foo
+scons.send('build %(_build_foo_exe_)s 2\n' % locals())
+
+test.wait_for(test.workpath('2'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = abs_foo_exe, stdout = 'Hello from foo.h\n')
+
+
+
+scons.send('exit\n')
+
+test.finish(scons)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option--Q.py b/test/Interactive/option--Q.py
new file mode 100644 (file)
index 0000000..4c02fa7
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of the -Q option on an individual "build" command
+will suppress the progress messages.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '--interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build -Q foo.out 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons>>> scons: Building targets ...
+Copy("foo.out", "foo.in")
+Touch("1")
+scons: done building targets.
+scons: Clearing cached node information ...
+scons: done clearing node information.
+scons>>> Copy("foo.out", "foo.in")
+Touch("2")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-i.py b/test/Interactive/option-i.py
new file mode 100644 (file)
index 0000000..a2935a7
--- /dev/null
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the -i option, specified on the build command, causes
+build errors to be ignored, just for that command.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+def error(target, source, env):
+    return 1
+e1 = Command('e1.out', 'e1.in', Action(error))
+e2 = Command('e2.out', 'e2.in', Action(error))
+f1 = Command('f1.out', 'f1.in', Copy('$TARGET', '$SOURCE'))
+f2 = Command('f2.out', 'f2.in', Copy('$TARGET', '$SOURCE'))
+Depends(f1, e1)
+Depends(f2, e2)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('e1.in', "e1.in\n")
+test.write('e2.in', "e2.in\n")
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive', combine=1)
+
+scons.send("build f1.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+
+
+
+scons.send("build -i e1.out f1.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('f1.out'), "f1.in\n")
+
+
+
+scons.send("build f2.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'), popen=scons)
+
+test.must_not_exist(test.workpath('f2.out'))
+
+
+
+expect_stdout = """\
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+scons>>> Touch("1")
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+Copy("f1.out", "f1.in")
+scons>>> Touch("2")
+scons>>> error(["e2.out"], ["e2.in"])
+scons: *** [e2.out] Error 1
+scons>>> Touch("3")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-j.py b/test/Interactive/option-j.py
new file mode 100644 (file)
index 0000000..29bba88
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that "build" command of --interactive mode can take a -j
+option to build things in parallel.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons(combine=1)
+
+test.write('SConstruct', """\
+import os
+import time
+from SCons.Script import *
+def cat(target, source, env):
+    t = str(target[0])
+    os.mkdir(t + '.started')
+    fp = open(t, 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+    os.mkdir(t + '.finished')
+
+def must_be_finished(target, source, env, dir):
+    if not os.path.exists(dir):
+        msg = 'build failed, %s does not exist\\n' % dir
+        sys.stderr.write(msg)
+        return 1
+    return cat(target, source, env)
+
+def f1_a_out_must_be_finished(target, source, env):
+    return must_be_finished(target, source, env, 'f1-a.out.finished')
+def f3_a_out_must_be_finished(target, source, env):
+    return must_be_finished(target, source, env, 'f3-a.out.finished')
+
+def must_wait_for_f2_b_out(target, source, env):
+    t = str(target[0])
+    os.mkdir(t + '.started')
+    f2_b_started = 'f2-b.out.started'
+    while not os.path.exists(f2_b_started):
+        time.sleep(1)
+    fp = open(t, 'wb')
+    for s in source:
+        fp.write(open(str(s), 'rb').read())
+    fp.close()
+    os.mkdir(t + '.finished')
+
+def _f2_a_out_must_not_be_finished(target, source, env):
+    f2_a_started = 'f2-a.out.started'
+    f2_a_finished = 'f2-a.out.finished'
+    while not os.path.exists(f2_a_started):
+        time.sleep(1)
+    msg = 'f2_a_out_must_not_be_finished(["%s"], ["%s"])\\n' % (target[0], source[0])
+    sys.stdout.write(msg)
+    if os.path.exists(f2_a_finished):
+        msg = 'build failed, %s exists\\n' % f2_a_finished
+        sys.stderr.write(msg)
+        return 1
+    return cat(target, source, env)
+
+f2_a_out_must_not_be_finished = Action(_f2_a_out_must_not_be_finished,
+                                       strfunction = None)
+
+Cat = Action(cat)
+f1_a = Command('f1-a.out', 'f1-a.in', cat)
+f1_b = Command('f1-b.out', 'f1-b.in', f1_a_out_must_be_finished)
+f2_a = Command('f2-a.out', 'f2-a.in', must_wait_for_f2_b_out)
+f2_b = Command('f2-b.out', 'f2-b.in', f2_a_out_must_not_be_finished)
+f3_a = Command('f3-a.out', 'f3-a.in', cat)
+f3_b = Command('f3-b.out', 'f3-b.in', f3_a_out_must_be_finished)
+Command('f1.out', f1_a + f1_b, cat)
+Command('f2.out', f2_a + f2_b, cat)
+Command('f3.out', f3_a + f3_b, cat)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('f1-a.in', "f1-a.in\n")
+test.write('f1-b.in', "f1-b.in\n")
+test.write('f2-a.in', "f2-a.in\n")
+test.write('f2-b.in', "f2-b.in\n")
+test.write('f3-a.in', "f3-a.in\n")
+test.write('f3-b.in', "f3-b.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build f1.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_match(test.workpath('f1.out'), "f1-a.in\nf1-b.in\n")
+
+
+
+scons.send("build -j2 f2.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('f2.out'), "f2-a.in\nf2-b.in\n")
+
+
+
+scons.send("build f3.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('f3.out'), "f3-a.in\nf3-b.in\n")
+
+
+
+expect_stdout = """\
+scons>>> cat(["f1-a.out"], ["f1-a.in"])
+f1_a_out_must_be_finished(["f1-b.out"], ["f1-b.in"])
+cat(["f1.out"], ["f1-a.out", "f1-b.out"])
+scons>>> Touch("1")
+scons>>> must_wait_for_f2_b_out(["f2-a.out"], ["f2-a.in"])
+f2_a_out_must_not_be_finished(["f2-b.out"], ["f2-b.in"])
+cat(["f2.out"], ["f2-a.out", "f2-b.out"])
+scons>>> Touch("2")
+scons>>> cat(["f3-a.out"], ["f3-a.in"])
+f3_a_out_must_be_finished(["f3-b.out"], ["f3-b.in"])
+cat(["f3.out"], ["f3-a.out", "f3-b.out"])
+scons>>> Touch("3")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-k.py b/test/Interactive/option-k.py
new file mode 100644 (file)
index 0000000..f15605e
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the -k option, specified on the build command, causes
+us to keep going and build additional targets.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+def error(target, source, env):
+    return 1
+e1 = Command('e1.out', 'e1.in', Action(error))
+f1 = Command('f1.out', 'f1.in', Copy('$TARGET', '$SOURCE'))
+Command('f2.out', 'f2.in', Copy('$TARGET', '$SOURCE'))
+Command('f3.out', 'f3.in', Copy('$TARGET', '$SOURCE'))
+Depends(f1, e1)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('e1.in', "e1.in\n")
+test.write('e2.in', "e2.in\n")
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive', combine=1)
+
+scons.send("build f1.out f2.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_not_exist(test.workpath('f2.out'))
+
+
+
+scons.send("build -k f1.out f2.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_match(test.workpath('f2.out'), "f2.in\n")
+
+
+
+scons.send("build f1.out f3.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_not_exist(test.workpath('f3.out'))
+
+
+
+expect_stdout = """\
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+scons>>> Touch("1")
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+Copy("f2.out", "f2.in")
+scons>>> Touch("2")
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+scons>>> Touch("3")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-n.py b/test/Interactive/option-n.py
new file mode 100644 (file)
index 0000000..f5ee1e3
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the -n option, specified on the build command, reports
+what would be built but doesn't actually build anything.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build -n foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-s.py b/test/Interactive/option-s.py
new file mode 100644 (file)
index 0000000..167d581
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the --interactive command line option
+to build a target, rebuild it when the input changes, and not rebuild
+it when the input doesn't change.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build -s foo.out 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> scons>>> scons: `foo.out' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/repeat-line.py b/test/Interactive/repeat-line.py
new file mode 100644 (file)
index 0000000..e85b9ea
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that a blank line repeats the last (build) command.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> build foo.out 1
+Copy("foo.out", "foo.in")
+scons: `1' is up to date.
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>> build foo.out
+scons: `foo.out' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/shell.py b/test/Interactive/shell.py
new file mode 100644 (file)
index 0000000..9d5e8a2
--- /dev/null
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability of the "shell" command (and its "sh" and "!" aliases)
+to shell out of interactive mode.
+"""
+
+import string
+import sys
+
+import TestSCons
+
+test = TestSCons.TestSCons(combine=1)
+
+_python_ = TestSCons._python_
+
+shell_command_py    = test.workpath('shell_command.py')
+_shell_command_py_  = '"%s"' % string.replace(shell_command_py, '\\', '\\\\')
+
+test.write(shell_command_py, """\
+print 'hello from shell_command.py'
+""")
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('!%(_python_)s %(_shell_command_py_)s\n' % locals())
+
+scons.send("\n")
+
+scons.send('shell %(_python_)s %(_shell_command_py_)s\n' % locals())
+
+scons.send("\n")
+
+scons.send('sh %(_python_)s %(_shell_command_py_)s\n' % locals())
+
+scons.send("\n")
+
+scons.send('!no_such_command arg1 arg2\n')
+
+scons.send("\n")
+
+scons.send("build foo.out\n")
+
+scons.send("\n")
+
+if sys.platform == 'win32':
+    no_such_error = 'The system cannot find the file specified'
+else:
+    no_such_error = 'No such file or directory'
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> hello from shell_command.py
+scons>>> !%(_python_)s %(_shell_command_py_)s
+hello from shell_command.py
+scons>>> hello from shell_command.py
+scons>>> shell %(_python_)s %(_shell_command_py_)s
+hello from shell_command.py
+scons>>> hello from shell_command.py
+scons>>> sh %(_python_)s %(_shell_command_py_)s
+hello from shell_command.py
+scons>>> scons: no_such_command: %(no_such_error)s
+scons>>> !no_such_command arg1 arg2
+scons: no_such_command: %(no_such_error)s
+scons>>> scons: `foo.out' is up to date.
+scons>>> build foo.out
+scons: `foo.out' is up to date.
+scons>>> 
+""" % locals()
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/taskmastertrace.py b/test/Interactive/taskmastertrace.py
new file mode 100644 (file)
index 0000000..23b9ad9
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify use of the --taskmastertrace= option to the "build" command
+of --interactive mode.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build --taskmastertrace=- foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> Taskmaster: 'foo.out': children:
+    ['foo.in']
+    waiting on unfinished children:
+    ['foo.in']
+Taskmaster: 'foo.in': evaluating foo.in
+Taskmaster: 'foo.out': children:
+    ['foo.in']
+    evaluating foo.out
+Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/tree.py b/test/Interactive/tree.py
new file mode 100644 (file)
index 0000000..96e7d4a
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the --interactive command line option to build
+a target, rebuild it when the input changes, and not rebuild it when
+the input doesn't change.
+
+Also tests that "scons" can be used as a synonym for "build".
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build --tree=all foo.out\n")
+
+scons.send("scons 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build --debug=tree foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
++-foo.out
+  +-foo.in
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
++-foo.out
+  +-foo.in
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/unknown-command.py b/test/Interactive/unknown-command.py
new file mode 100644 (file)
index 0000000..2b77378
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the error message when an unknown command is typed in.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('this-is-an-unknown-command hello\n')
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> *** Unknown command: this-is-an-unknown-command
+scons>>> 
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/version.py b/test/Interactive/version.py
new file mode 100644 (file)
index 0000000..84f70e8
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the behavior of the "version" subcommand.
+"""
+
+import TestCmd
+import TestSCons
+
+test = TestSCons.TestSCons(match = TestCmd.match_re)
+
+test.write('SConstruct', "")
+
+
+
+# Construct the standard copyright marker so it doesn't get replaced
+# by the packaging build.
+copyright_marker = '__' + 'COPYRIGHT' + '__'
+
+copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007'
+
+fmt = '(%s|Copyright \\(c\\) %s The SCons Foundation)\n'
+
+copyright_line = fmt % (copyright_marker, copyright_years)
+
+
+
+expect1 = """\
+scons>>> 
+scons>>> 
+"""
+
+expect2 = """\
+scons>>> 
+scons>>> 
+"""
+
+test.run(arguments = '-Q --interactive',
+         stdin = "version\nexit\n")
+
+# Windows may or may not print a line for the script version
+# depending on whether it's invoked through scons.py or scons.bat.
+expect1 = r"""scons>>> SCons by Steven Knight et al\.:
+\tengine: v\S+, [^,]*, by \S+ on \S+
+%(copyright_line)sscons>>> 
+""" % locals()
+
+expect2 = r"""scons>>> SCons by Steven Knight et al\.:
+\tscript: v\S+, [^,]*, by \S+ on \S+
+\tengine: v\S+, [^,]*, by \S+ on \S+
+%(copyright_line)sscons>>> 
+""" % locals()
+
+stdout = test.stdout() + '\n'
+if not test.match_re(stdout, expect1) and not test.match_re(stdout, expect2):
+    print repr(stdout)
+    test.fail_test()
+
+
+
+test.pass_test()
index f0951c62bca4bc5be4e8420ce83a8c8c728ae162..ee552f45ee22d0cc216c67f115577fd13889b02c 100644 (file)
@@ -120,21 +120,11 @@ test.run(arguments='classes.jar')
 test.must_match('classes.jar',
                 'cvfm classes.jar foo.mf -C testdir bar.class\n')
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
-    where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
-    where_jar = test.where_is('jar')
-if not where_jar:
-    test.skip_test("Could not find Java jar, skipping test(s).\n")
+
+
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
+
 
 
 test.write("wrapper.py", """\
index f7d9fca17675ca69459a8ac7b409581c613499dd..a3f2ec4f0adb5dc7dd892211b4b08bd3b4f02181 100644 (file)
@@ -40,21 +40,8 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
-    where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
-    where_jar = test.where_is('jar')
-if not where_jar:
-    test.skip_test("Could not find Java jar, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
 
 
 
index 3939d98301147979521ec136a02499c368aa8a54..03a222b819a6b53c549648cb51986cea0c3add83 100644 (file)
@@ -33,21 +33,10 @@ test = TestSCons.TestSCons()
 
 test.subdir('src')
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
-    where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
-    where_jar = test.where_is('jar')
-if not where_jar:
-    test.skip_test("Could not find Java jar, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
+
+
 
 test.write('SConstruct', """
 env = Environment(tools = ['javac', 'jar'],
@@ -75,9 +64,9 @@ public class Example1
 
 expect = test.wrap_stdout("""\
 %(where_javac)s -d classes -sourcepath src src/Example1\.java
-%(where_jar)s cvf test.jar classes/src/Example1\.class
+%(where_jar)s cvf test.jar -C classes src/Example1\.class
 .*
-adding: classes/src/Example1\.class.*
+adding: src/Example1\.class.*
 """ % locals())
 
 expect = string.replace(expect, '/', os.sep)
index 7723224f15ddfee7559c4564381cf222f905a778..5962b9457679114eb3e44088d0a7b678c7d427cf 100644 (file)
@@ -38,21 +38,8 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
-    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
-    where_javah = test.where_is('javah')
-if not where_javah:
-    test.skip_test("Could not find Java javah, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
 
 test.write('SConstruct', """
 env = Environment(tools = ['javac', 'javah'],
index 045fb7b36e84d45726591e4c34b06df64e04e86a..e2870549054953ee0a66006b01aba803a6ca39e4 100644 (file)
@@ -31,14 +31,7 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
 
 test.subdir('src')
 
index 0ae7dea51f4166c7026812fa6c3f75c33fe0365b..11f7c2c4be2ddda08dee427cd5ad93a8065d673d 100644 (file)
@@ -36,21 +36,8 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
-    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
-    where_javah = test.where_is('javah')
-if not where_javah:
-    test.skip_test("Could not find Java javah, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
 
 test.write('SConstruct', """
 env = Environment(tools = ['javac', 'javah'],
index ecd3737fb4000bc4519468d18f5b4dcaafc6b20b..95abd33ff7a8a0beabd78828b808d5bd5ee062a3 100644 (file)
@@ -24,9 +24,9 @@
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import os
+import os.path
 import string
-import sys
+
 import TestSCons
 
 _python_ = TestSCons._python_
@@ -95,21 +95,11 @@ line 3
 
 
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
 
-if test.detect_tool('javah', ENV=ENV):
-    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
-    where_javah = test.where_is('javah')
-if not where_javah:
-    test.skip_test("Could not find Java javah, skipping test(s).\n")
+if java_version:
+    java_version = repr(java_version)
 
 
 
@@ -125,6 +115,9 @@ test.write('SConstruct', """
 foo = Environment(tools = ['javac', 'javah', 'install'],
                   JAVAC = r'%(where_javac)s',
                   JAVAH = r'%(where_javah)s')
+jv = %(java_version)s
+if jv:
+    foo['JAVAVERSION'] = jv
 javah = foo.Dictionary('JAVAH')
 bar = foo.Clone(JAVAH = r'%(_python_)s wrapper.py ' + javah)
 foo.Java(target = 'class1', source = 'com/sub/foo')
index 069e228274c515fbabff7d5b7a8dff21d1fd4717..87e90add02226d02dfff0ccb6e66511df10a18a6 100644 (file)
@@ -36,14 +36,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
 
 test.write('SConstruct', """
 env = Environment(tools = ['javac', 'javah'],
index 3d3d47abd3f087905c8d8f33183f09081291d7a6..3c0e1ed8c9eb08bba5b5d9b7062f0717b23c372c 100644 (file)
@@ -38,14 +38,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac('1.4')
 
 
 
@@ -234,10 +227,10 @@ public class NestedExample
 {
         public NestedExample()
         {
-                Thread t = new Thread() {
+                new Thread() {
                         public void start()
                         {
-                                Thread t = new Thread() {
+                                new Thread() {
                                         public void start()
                                         {
                                                 try {Thread.sleep(200);}
@@ -256,7 +249,7 @@ public class NestedExample
 
         public static void main(String argv[])
         {
-                NestedExample e = new NestedExample();
+                new NestedExample();
         }
 }
 """)
@@ -268,7 +261,7 @@ public class NestedExample
 test.write(['src5', 'TestSCons.java'], """\
 class TestSCons {
     public static void main(String[] args) {
-        Foo[] fooArray = new Foo[] { new Foo() };
+        new Foo();
     }
 }
 
@@ -355,12 +348,16 @@ def classes_must_match(dir, expect):
     global failed
     got = get_class_files(test.workpath(dir))
     if expect != got:
-        sys.stderr.write("Expected the following class files in '%s':\n" % dir)
-        for c in expect:
-            sys.stderr.write('    %s\n' % c)
-        sys.stderr.write("Got the following class files in '%s':\n" % dir)
-        for c in got:
-            sys.stderr.write('    %s\n' % c)
+        missing = set(expect) - set(got)
+        if missing:
+            sys.stderr.write("Missing the following class files from '%s':\n" % dir)
+            for c in missing:
+                sys.stderr.write('    %s\n' % c)
+        unexpected = set(got) - set(expect)
+        if unexpected:
+            sys.stderr.write("Found the following unexpected class files in '%s':\n" % dir)
+            for c in unexpected:
+                sys.stderr.write('    %s\n' % c)
         failed = 1
 
 def classes_must_not_exist(dir, expect):
index 4ac3d96c3a0173a2e70fd19acc53f122bead0f4c..f6d93c7a627bc9edcaa0aeb364f4436eeb2b64c5 100644 (file)
@@ -38,15 +38,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-ENV['PATH'] = '/usr/lib/jvm/java-1.5.0-sun-1.5.0.11/bin' + os.pathsep + os.environ['PATH']
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac('1.5')
 
 
 
@@ -235,10 +227,10 @@ public class NestedExample
 {
         public NestedExample()
         {
-                Thread t = new Thread() {
+                new Thread() {
                         public void start()
                         {
-                                Thread t = new Thread() {
+                                new Thread() {
                                         public void start()
                                         {
                                                 try {Thread.sleep(200);}
@@ -257,7 +249,7 @@ public class NestedExample
 
         public static void main(String argv[])
         {
-                NestedExample e = new NestedExample();
+                new NestedExample();
         }
 }
 """)
index f2b629aff76a89b8565f96789306fdd07f73a725..5bd8e2f59db44d93d70c0b27d6f875d3a6b6cfab 100644 (file)
@@ -38,15 +38,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-ENV['PATH'] = '/usr/lib/jvm/java-6-sun-1.6.0.00/bin' + os.pathsep + os.environ['PATH']
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac('1.6')
 
 
 
@@ -235,10 +227,10 @@ public class NestedExample
 {
         public NestedExample()
         {
-                Thread t = new Thread() {
+                new Thread() {
                         public void start()
                         {
-                                Thread t = new Thread() {
+                                new Thread() {
                                         public void start()
                                         {
                                                 try {Thread.sleep(200);}
@@ -257,7 +249,7 @@ public class NestedExample
 
         public static void main(String argv[])
         {
-                NestedExample e = new NestedExample();
+                new NestedExample();
         }
 }
 """)
index 2ab1804e7a697fc82d585e498bd12bc0db67fe2e..f9721c2f0adb6b99ca83cddbdc9ec631aa861a82 100644 (file)
@@ -92,21 +92,8 @@ line 3
 
     test.fail_test(test.read(['outdir', 'test2.class']) != "test2.JAVA\nline 3\n")
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping non-simulated test(s).\n")
-
-if test.detect_tool('rmic', ENV=ENV):
-    where_rmic = test.detect('RMIC', 'rmic', ENV=ENV)
-else:
-    where_rmic = test.where_is('rmic')
-if not where_rmic:
-    test.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_rmic = test.java_where_rmic()
 
 test.write("wrapper.py", """\
 import os
@@ -319,15 +306,20 @@ test.run(arguments = '.')
 
 test.fail_test(test.read('wrapper.out') != "wrapper.py %s -d outdir2 -classpath class2 com.sub.bar.Example3 com.sub.bar.Example4\n" % where_rmic)
 
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Stub.class')))
-
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Stub.class')))
+test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Stub.class'))
+test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Stub.class'))
+test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Stub.class'))
+test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Stub.class'))
+
+# We used to check for _Skel.class files as well, but they're not
+# generated by default starting with Java 1.5, and they apparently
+# haven't been needed for a while.  Don't bother looking, even if we're
+# running Java 1.4.  If we think they're needed but they don't exist
+# the test.up_to_date() call below will detect it.
+#test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Skel.class'))
+#test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Skel.class'))
+#test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Skel.class'))
+#test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Skel.class'))
 
 test.up_to_date(arguments = '.')
 
index ed5e0d674972c3a52f5809889efde19997097064..e8a56553e2a492d748b4af70576eb47f5370ed8a 100644 (file)
@@ -40,9 +40,9 @@ test.subdir('src')
 
 
 
-out_file1 = os.path.join('out', 'file1', 'class_Skel.class')
-out_file2 = os.path.join('out', 'file2', 'class_Skel.class')
-out_file3 = os.path.join('out', 'file3', 'class_Skel.class')
+out_file1 = os.path.join('out', 'file1', 'class_Stub.class')
+out_file2 = os.path.join('out', 'file2', 'class_Stub.class')
+out_file3 = os.path.join('out', 'file3', 'class_Stub.class')
 
 
 
index 5a451ebd1c7f94f208034e7b2497b70757e22a61..a92bac53811f202b24873956ae88861b5b890725 100644 (file)
@@ -41,9 +41,9 @@ test.subdir('src')
 
 
 
-out_file1 = os.path.join('out', 'file1', 'class_Skel.class')
-out_file2 = os.path.join('out', 'file2', 'class_Skel.class')
-out_file3 = os.path.join('out', 'file3', 'class_Skel.class')
+out_file1 = os.path.join('out', 'file1', 'class_Stub.class')
+out_file2 = os.path.join('out', 'file2', 'class_Stub.class')
+out_file3 = os.path.join('out', 'file3', 'class_Stub.class')
 
 
 
index d185b4d56c4f51f186b3477bb668c1f8a3c4cc2b..9cac75972025ee030da2282dc6ac2d641fde266d 100644 (file)
@@ -33,22 +33,8 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-# This test requires javac and swig
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
-    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
-    where_javah = test.where_is('javah')
-if not where_javah:
-    test.skip_test("Could not find Java javah, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
 
 swig = test.where_is('swig')
 if not swig:
diff --git a/test/Java/no-JARCHDIR.py b/test/Java/no-JARCHDIR.py
new file mode 100644 (file)
index 0000000..795689c
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the Jar() behavior when we have no JARCHDIR set (it should
+automatically use the classdir that was deduced from the Java() call)
+and when we explicity set it to None (it should not use the Java()
+classdir attribute at all).
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
+
+test.subdir('src')
+
+
+
+test.write(['src', 'a.java'], """\
+package foo.bar;
+public class a {}
+""")
+
+test.write(['src', 'b.java'], """\
+package foo.bar;
+public class b {}
+""")
+
+
+
+test.write('SConstruct', """\
+env = Environment(tools    = ['javac', 'jar'],
+                  JAVAC = r'%(where_javac)s',
+                  JAR = r'%(where_jar)s')
+
+jar = env.Jar('x.jar', env.Java(target = 'classes', source = 'src'))
+""" % locals())
+
+test.run(arguments = '.')
+
+
+
+test.run(program = where_jar, arguments = 'tf x.jar')
+
+expect = """\
+foo/bar/a.class
+foo/bar/b.class
+"""
+
+if string.find(test.stdout(), expect) == -1:
+    print "Did not find expected string in standard output."
+    print "Expected =========================================================="
+    print expect
+    print "Output ============================================================"
+    print test.stdout()
+    test.fail_test()
+
+
+
+test.run(arguments = '-c')
+
+
+
+test.write('SConstruct', """\
+env = Environment(tools    = ['javac', 'jar'],
+                  JAVAC = r'%(where_javac)s',
+                  JAR = r'%(where_jar)s',
+                  JARCHDIR = None)
+
+jar = env.Jar('x.jar', env.Java(target = 'classes', source = 'src'))
+""" % locals())
+
+test.run(arguments = '.')
+
+
+
+test.run(program = where_jar, arguments = 'tf x.jar')
+
+expect = """\
+classes/foo/bar/a.class
+classes/foo/bar/b.class
+"""
+
+if string.find(test.stdout(), expect) == -1:
+    print "Did not find expected string in standard output."
+    print "Expected =========================================================="
+    print expect
+    print "Output ============================================================"
+    print test.stdout()
+    test.fail_test()
+
+
+
+test.pass_test()
index 8d2506fb4973170427604ee4c55c78516dd1a50a..63d1d92aa3b3f18b55902bd8067b177b73743935 100644 (file)
@@ -35,14 +35,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
 
 
 test.write('SConstruct', """
index 8df5e0965d7bef8de06acaf0a81da6c462613fbe..5477a2d4dc4f9815c2453eb32768a467e4a0cae4 100644 (file)
@@ -34,29 +34,14 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
-    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
-    where_javah = test.where_is('javah')
-if not where_javah:
-    test.skip_test("Could not find Java javah, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
-    where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
-    where_jar = test.where_is('jar')
-if not where_jar:
-    test.skip_test("Could not find Java jar, skipping test(s).\n")
+swig = test.where_is('swig')
 
+if not swig:
+    test.skip_test('Can not find installed "swig", skipping test.\n')
+
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
+where_jar = test.java_where_jar()
 
 test.subdir(['foo'],
             ['java'],
similarity index 100%
rename from test/LIBPATH.py
rename to test/Libs/LIBPATH.py
similarity index 100%
rename from test/LIBPREFIX.py
rename to test/Libs/LIBPREFIX.py
similarity index 100%
rename from test/LIBPREFIXES.py
rename to test/Libs/LIBPREFIXES.py
similarity index 100%
rename from test/LIBS.py
rename to test/Libs/LIBS.py
similarity index 100%
rename from test/LIBSUFFIX.py
rename to test/Libs/LIBSUFFIX.py
similarity index 100%
rename from test/LIBSUFFIXES.py
rename to test/Libs/LIBSUFFIXES.py
similarity index 100%
rename from test/Library.py
rename to test/Libs/Library.py
similarity index 100%
rename from test/SHLIBPREFIX.py
rename to test/Libs/SHLIBPREFIX.py
similarity index 100%
rename from test/SHLIBSUFFIX.py
rename to test/Libs/SHLIBSUFFIX.py
diff --git a/test/Libs/SharedLibraryIxes.py b/test/Libs/SharedLibraryIxes.py
new file mode 100644 (file)
index 0000000..f9d1471
--- /dev/null
@@ -0,0 +1,261 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test that we can build shared libraries and link against shared
+libraries that have non-standard library prefixes and suffixes.
+"""
+
+import re
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import sys
+isWindows = sys.platform == 'win32'
+
+env = Environment()
+
+# Make sure that the shared library can be located at runtime.
+env.Append(RPATH=['.'])
+env.Append(LIBPATH=['.'])
+
+# We first bake the LIBSUFFIXES, so that it will not change as a
+# side-effect of changing SHLIBSUFFIX.
+env['LIBSUFFIXES'] = map( env.subst, env.get('LIBSUFFIXES', []))
+
+weird_prefixes = ['libXX', 'libYY']
+
+if isWindows:
+    weird_suffixes = ['.xxx', '.yyy', '.xxx.dll', '.yyy.dll']
+    env.Append(CCFLAGS = '/MD')
+elif env['PLATFORM'] == 'darwin':
+    weird_suffixes = ['.xxx.dylib', '.yyy.dylib']
+else:
+    weird_suffixes = ['.xxx.so', '.yyy.so']
+
+shlibprefix = env.subst('$SHLIBPREFIX')
+shlibsuffix = env.subst('$SHLIBSUFFIX')
+
+progprefix = env.subst('$PROGPREFIX')
+progsuffix = env.subst('$PROGSUFFIX')
+
+goo_obj = env.SharedObject(source='goo.c')
+foo_obj = env.SharedObject(source='foo.c')
+prog_obj = env.SharedObject(source='prog.c')
+
+#
+# The following functions define all the different way that one can
+# use link againt a shared library.
+#
+def nodeInSrc(source, lib, libname):
+    return (source+lib, '')
+
+def pathInSrc(source, lib, libname):
+    return (source+map(str,lib), '')
+
+def nodeInLib(source, lib, libname):
+    return (source, lib)
+
+def pathInLib(source, lib, libname):
+    return (source, map(str,lib))
+
+def nameInLib(source, lib, libname):
+    # NOTE: libname must contain both the proper prefix and suffix.
+    #
+    # When using non-standard prefixes and suffixes, one has to
+    # provide the full name of the library since scons can not know
+    # which of the non-standard extension to use.
+    # 
+    # Note that this is not necessarally SHLIBPREFIX and
+    # SHLIBSUFFIX. These are the ixes of the target library, not the
+    # ixes of the library that we are linking againt.
+    return (source, libname)
+
+libmethods = [
+    nodeInSrc, pathInSrc, nodeInLib, pathInLib, 
+    nameInLib ]
+
+def buildAndlinkAgainst(builder, target, source,  method, lib, libname, **kw):
+    '''Build a target using a given builder while linking againt a given
+    library using a specified method for linking against the library.'''
+
+    # On Windows, we have to link against the .lib file.
+    if isWindows:
+        for l in lib:
+            if str(l)[-4:] == '.lib':
+                lib = [l]
+                break
+    (source, LIBS) = method(source, lib, libname)
+    #build = builder(target=target, source=source, LIBS=LIBS, **kw)
+    kw = kw.copy()
+    kw['target'] = target
+    kw['source'] = source
+    kw['LIBS'] = LIBS
+    build = apply(builder, (), kw)
+
+    # Check that the build target depends on at least one of the
+    # library target.
+    found_dep = False
+    children = build[0].children()
+    for l in lib:
+        if l in children:
+            found_dep = True
+            break;
+    assert found_dep, \
+        "One of %s not found in %s, method=%s, libname=%s, shlibsuffix=%s" % \
+        (map(str,lib), map(str, build[0].children()), method.__name__, libname, shlibsuffix)
+    return build
+
+def prog(i, 
+         goomethod, goolibprefix, goolibsuffix, 
+         foomethod, foolibprefix, foolibsuffix):
+    '''Build a program
+
+     The program links against a shared library foo which itself links
+     against a shared library goo. The libraries foo and goo can use
+     arbitrary library prefixes and suffixes.'''
+
+    goo_name =  goolibprefix+'goo'+str(i)+goolibsuffix
+    foo_name =  foolibprefix+'foo'+str(i)+foolibsuffix
+    prog_name = progprefix+'prog'+str(i)+progsuffix
+
+    print 'Prog: %d, %s, %s, %s' % (i, goo_name, foo_name, prog_name)
+
+    # On Windows, we have to link against the .lib file.
+    if isWindows:
+        goo_libname =  goolibprefix+'goo'+str(i)+'.lib'
+        foo_libname =  foolibprefix+'foo'+str(i)+'.lib'
+    else:
+        goo_libname =  goo_name
+        foo_libname =  foo_name
+
+    goo_lib = env.SharedLibrary(
+        goo_name, goo_obj, SHLIBSUFFIX=goolibsuffix)
+    foo_lib = buildAndlinkAgainst(
+        env.SharedLibrary, foo_name, foo_obj, 
+        goomethod, goo_lib, goo_libname, SHLIBSUFFIX=foolibsuffix)
+    prog = buildAndlinkAgainst(env.Program, prog_name, prog_obj,
+        foomethod, foo_lib, foo_libname)
+
+
+#
+# Create the list of all possible permutations to test.
+#
+i = 0
+tests = []
+prefixes = [shlibprefix] +  weird_prefixes
+suffixes = [shlibsuffix] +  weird_suffixes
+for foolibprefix in prefixes:
+    for foolibsuffix in suffixes:
+        for foomethod in libmethods:
+            for goolibprefix in prefixes:
+                for goolibsuffix in suffixes:
+                    for goomethod in libmethods:
+                        tests.append(
+                            (i, 
+                             goomethod, goolibprefix, goolibsuffix, 
+                             foomethod, foolibprefix, foolibsuffix))
+                        i = i + 1
+
+#
+# Pseudo-randomly choose 200 tests to run out of the possible
+# tests. (Testing every possible permutation would take too long.)
+#
+import random
+random.seed(123456)
+try:
+    random.shuffle(tests)
+except AttributeError:
+    pass
+
+for i in range(200):
+  apply(prog, tests[i])
+
+""")
+
+test.write('goo.c', r"""
+#include <stdio.h>
+
+#ifdef _WIN32
+#define EXPORT __declspec( dllexport )
+#else
+#define EXPORT
+#endif
+
+EXPORT void
+goo(void)
+{
+        printf("goo.c\n");
+}
+""")
+
+test.write('foo.c', r"""
+#include <stdio.h>
+
+#ifdef _WIN32
+#define EXPORT __declspec( dllexport )
+#else
+#define EXPORT
+#endif
+
+EXPORT void
+foo(void)
+{
+        goo();
+        printf("foo.c\n");
+}
+""")
+
+test.write('prog.c', r"""
+#include <stdio.h>
+
+void foo(void);
+int
+main(int argc, char *argv[])
+{
+        argv[argc++] = "--";
+        foo();
+        printf("prog.c\n");
+        return 0;
+}
+""")
+
+test.run(arguments = '.',
+         stderr=TestSCons.noisy_ar,
+         match=TestSCons.match_re_dotall)
+
+tests = re.findall(r'Prog: (\d+), (\S+), (\S+), (\S+)', test.stdout())
+expected = "goo.c\nfoo.c\nprog.c\n"
+
+for t in tests:
+    test.must_exist(t[1])
+    test.must_exist(t[2])
+    test.must_exist(t[3])
+    test.run(program = test.workpath(t[3]), stdout=expected)
+
+test.pass_test()
index 5243fbf4011e05c3b023155d85f27ef2c83f4ff7..4a8e1eda6bd04bdcf105fb51ec64b3fc7b677916 100644 (file)
@@ -105,7 +105,8 @@ test.run(arguments = '.',
 if string.find(sys.platform, 'darwin') != -1:
     test.run(program='/usr/bin/file',
              arguments = "foo1",
-             stdout="foo1: Mach-O bundle ppc\n")
+             match = TestCmd.match_re,
+             stdout="foo1: Mach-O bundle (ppc|i386)\n")
 
 if sys.platform in platforms_with_dlopen:
     os.environ['LD_LIBRARY_PATH'] = test.workpath()
diff --git a/test/MSVC/pdb-manifest.py b/test/MSVC/pdb-manifest.py
new file mode 100644 (file)
index 0000000..00f3ee2
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that .pdb files work correctly in conjunction with manifest files.
+"""
+
+import sys
+
+import TestSCons
+
+_exe = TestSCons._exe
+_dll = TestSCons._dll
+_lib = TestSCons._lib
+
+test = TestSCons.TestSCons()
+
+if sys.platform != 'win32':
+    msg = "Skipping Visual C/C++ test on non-Windows platform '%s'\n" % sys.platform
+    test.skip_test(msg)
+
+test.write('SConstruct', """\
+env = Environment()
+
+env['WINDOWS_INSERT_DEF'] = True
+env['WINDOWS_INSERT_MANIFEST'] = True
+env['PDB'] = '${TARGET.base}.pdb'
+env.Program('test', 'test.cpp')
+env.SharedLibrary('sharedlib', 'test.cpp')
+env.StaticLibrary('staticlib', 'test.cpp')
+""")
+
+test.write('test.cpp', """\
+#include <stdio.h>
+#include <stdlib.h>
+int
+main(int argc, char *argv)
+{
+    printf("test.cpp\\n");
+    exit (0);
+}
+""")
+
+test.write('sharedlib.def', """\
+""")
+
+test.run(arguments = '.')
+
+test.must_exist('test%s' % _exe)
+test.must_exist('test.pdb')
+
+test.must_exist('sharedlib%s' % _dll)
+test.must_exist('sharedlib.pdb')
+
+test.must_exist('staticlib%s' % _lib)
+test.must_not_exist('staticlib.pdb')
+
+test.pass_test()
index 28781369471dcdf03eb574a8f51a184b489cb093..993649069aeff68d5590ef40d5c7ce9051d1f21f 100644 (file)
@@ -55,6 +55,8 @@ test.Qt_create_SConstruct('SConstruct')
 test.write('SConscript', """\
 Import("env")
 env.Program(target = 'aaa', source = 'aaa.cpp')
+if env['PLATFORM'] == 'darwin':
+    env.Install('.', 'qt/lib/libmyqt.dylib')
 """)
 
 test.write('aaa.cpp', r"""
@@ -67,7 +69,7 @@ test.write('aaa.h', r"""
 void aaa(void) Q_OBJECT;
 """)
 
-test.run(arguments = aaa_exe)
+test.run()
 
 test.up_to_date(options = '-n', arguments=aaa_exe)
 
index baa1e6faf3f5f80101e3dc430937213a4c13c977..ef94dd6f49d868e3fee24c75c3a32e54cf0974c5 100644 (file)
@@ -59,9 +59,10 @@ match12 = r"""
 scons: warning: Generated moc file 'aaa.moc' is not included by 'aaa.cpp'
 """ + TestSCons.file_expr
 
-# In case 'ar' gives a warning about creating a library.
-test.fail_test(not test.match_re(test.stderr(), match12) and \
-               not test.match_re(test.stderr(), match12 + ".+\n"))
+if not re.search(match12, test.stderr()):
+    print "Did not find expected regular expression in stderr:"
+    print test.stderr()
+    test.fail_test()
 
 os.environ['QTDIR'] = test.QT
 
index 6e8bbd6b9c9e1b655459f3669014fba801078fa6..67ef605d2be98c5aeb0e23e92d1d428db54320ff 100644 (file)
@@ -37,18 +37,8 @@ python = TestSCons.python
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-where_java = test.where_is('java')
-if not where_java:
-    test.skip_test("Could not find Java java, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_java = test.java_where_java()
 
 
 java = where_java
@@ -115,6 +105,8 @@ test.writable('repository', 0)
 #
 test.run(chdir = 'work1', options = opts, arguments = ".")
 
+os.environ['JAVA_HOME'] = '/usr/lib/jvm/java-1.5.0-sun-1.5.0.11'
+
 test.run(program = java,
          arguments = "-cp %s Foo1" % work1_classes,
          stdout = "rep1/src/Foo1.java\n")
index fc8790413b04442a1493ff8a69a5ff716ce8f773..258d9bd1620243c34a388a828143c1d3d1240564 100644 (file)
@@ -37,25 +37,9 @@ python = TestSCons.python
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
-    where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
-    where_javah = test.where_is('javah')
-if not where_javah:
-    test.skip_test("Could not find Java javah, skipping test(s).\n")
-
-where_java = test.where_is('java')
-if not where_java:
-    test.skip_test("Could not find Java java, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_java = test.java_where_java()
+where_javah = test.java_where_javah()
 
 java = where_java
 javac = where_javac
index 9216a8b3ce5114b7ba7a2f8cf3645312597e0b8b..0f80acef2fd45c002aa8dbbe2882aee82db89daa 100644 (file)
@@ -59,6 +59,13 @@ def write_LIBDIRFLAGS(env, target, source):
     return 0
 env_zzz.Command('zzz.out', aaa_exe, write_LIBDIRFLAGS)
 env_yyy.Command('yyy.out', bbb_exe, write_LIBDIRFLAGS)
+
+if env_yyy['PLATFORM'] == 'darwin':
+    # The Mac OS X linker complains about nonexistent directories
+    # specified as -L arguments.  Suppress its warnings so we don't
+    # treat the warnings on stderr as a failure.
+    env_yyy.Append(LINKFLAGS=['-w'])
+    env_zzz.Append(LINKFLAGS=['-w'])
 """)
 
 test.write(['work', 'aaa.c'], r"""
index ebe6a83d968bc465c3256acd279ae7d75e0bf73d..bf8edffd8f32bc3c31ededc323b38fccb1d9effd 100644 (file)
@@ -28,34 +28,17 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 Test building Java applications when using Repositories.
 """
 
-import os
 import string
-import sys
+
 import TestSCons
 
 python = TestSCons.python
 
 test = TestSCons.TestSCons()
 
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
-    where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
-    where_javac = test.where_is('javac')
-if not where_javac:
-    test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('rmic', ENV=ENV):
-    where_rmic = test.detect('RMIC', 'rmic', ENV=ENV)
-else:
-    where_rmic = test.where_is('rmic')
-if not where_rmic:
-    test.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
-
-where_java = test.where_is('java')
-if not where_java:
-    test.skip_test("Could not find Java java, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_java = test.java_where_java()
+where_rmic = test.java_where_rmic()
 
 java = where_java
 javac = where_javac
@@ -185,15 +168,20 @@ test.run(chdir = 'work1', options = opts, arguments = ".")
 # see that they were built from the proper rep1 sources, but I don't
 # know how to do that with RMI, so punt for now.
 
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
 
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+
+# We used to check for _Skel.class files as well, but they're not
+# generated by default starting with Java 1.5, and they apparently
+# haven't been needed for a while.  Don't bother looking, even if we're
+# running Java 1.4.  If we think they're needed but they don't exist
+# the variou test.up_to_date() calls below will detect it.
+#test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
+#test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
 
 test.up_to_date(chdir = 'work1', options = opts, arguments = ".")
 
@@ -294,15 +282,15 @@ test.fail_test(string.find(test.stdout(), ' com.sub.foo.Foo1 com.sub.foo.Foo2')
 # see that they were built from the proper work1 sources, but I don't
 # know how to do that with RMI, so punt for now.
 
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
 
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+#test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
+#test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist    (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
 
 test.up_to_date(chdir = 'work1', options = opts, arguments = ".")
 
@@ -315,10 +303,11 @@ test.run(chdir = 'rep1', options = opts, arguments = ".")
 # see that they were built from the proper work1 sources, but I don't
 # know how to do that with RMI, so punt for now.
 
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+
+#test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
 
 test.up_to_date(chdir = 'rep1', options = opts, arguments = ".")
 
@@ -343,13 +332,16 @@ Local(rmi_classes)
 
 test.run(chdir = 'work3', options = opts, arguments = ".")
 
-test.fail_test(os.path.exists(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Hello.class')))
-test.fail_test(os.path.exists(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo1.class')))
-test.fail_test(os.path.exists(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo2.class')))
+test.must_not_exist(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Hello.class'))
+test.must_not_exist(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo1.class'))
+test.must_not_exist(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo2.class'))
+
+test.must_exist    (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist    (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+
+#test.must_exist    (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist    (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
 
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.up_to_date(chdir = 'work3', options = opts, arguments = ".")
 
 test.pass_test()
diff --git a/test/SWIG/module-parens.py b/test/SWIG/module-parens.py
new file mode 100644 (file)
index 0000000..0d89ebe
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we handle %module(directors="1") statements, both with and
+without white space before the opening parenthesis.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+swig = test.where_is('swig')
+
+if not swig:
+    test.skip_test('Can not find installed "swig", skipping test.\n')
+
+python_include_dir = test.get_python_inc()
+
+test.write(['SConstruct'], """\
+env = Environment(SWIGFLAGS = '-python',
+                  CPPPATH=r"%(python_include_dir)s")
+
+import sys
+if sys.version[0] == '1':
+    # SWIG requires the -classic flag on pre-2.0 Python versions.
+    env.Append(SWIGFLAGS = ' -classic')
+
+env.SharedLibrary('test1.so', 'test1.i')
+env.SharedLibrary('test2.so', 'test2.i')
+""" % locals())
+
+test.write(['test1.cc'], """\
+int test1func()
+{
+  return 0;
+}
+""")
+
+test.write(['test1.h'], """\
+int test1func();
+""")
+
+test.write(['test1.i'], """\
+%module(directors="1") test1
+
+%{
+#include "test1.h"
+%}
+
+%include "test1.h"
+""")
+
+test.write(['test2.cc'], """\
+int test2func()
+{
+  return 0;
+}
+""")
+
+test.write(['test2.h'], """\
+int test2func();
+""")
+
+test.write(['test2.i'], """\
+%module (directors="1") test2
+
+%{
+#include "test2.h"
+%}
+
+%include "test2.h"
+""")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
index dd2c938cf496b8f1b60df64ad8094ab9d590bf7e..ef91189fe11832c5dad074c3d052cca987809760 100644 (file)
@@ -318,10 +318,10 @@ def write_out(file, dict):
         f.write(file + ": " + str(dict[k]) + "\\n")
     f.close()
 
-orig_function = CScan.scan
+orig_function = CScan.__call__
 
-def MyCScan(node, paths, orig_function=orig_function):
-    deps = orig_function(node, paths)
+def MyCScan(node, paths, cwd, orig_function=orig_function):
+    deps = orig_function(node, paths, cwd)
 
     global Scanned
     n = str(node)
@@ -333,7 +333,7 @@ def MyCScan(node, paths, orig_function=orig_function):
 
     return deps
 
-CScan.scan = MyCScan
+CScan.__call__ = MyCScan
 
 env = Environment(CPPPATH = ".")
 l = env.StaticLibrary("g", Split("libg_1.c libg_2.c libg_3.c"))
diff --git a/test/TEX/build_dir_dup0.py b/test/TEX/build_dir_dup0.py
new file mode 100644 (file)
index 0000000..8035957
--- /dev/null
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test creation of a fully-featured TeX document (with bibliography
+and index) in a build_dir.
+
+Test courtesy Rob Managan.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+latex = test.where_is('latex')
+dvipdf = test.where_is('dvipdf')
+makeindex = test.where_is('makeindex')
+bibtex = test.where_is('bibtex')
+if not latex or not makeindex or not bibtex or not dvipdf:
+    test.skip_test("Could not find 'latex', 'makeindex', 'bibtex', or dvipdf; skipping test.\n")
+
+test.subdir(['docs'])
+
+
+test.write(['SConstruct'], """\
+import os
+
+env = Environment(ENV = { 'PATH' : os.environ['PATH'] },
+                  TOOLS = ['tex', 'latex', 'dvipdf'])
+Export(['env'])
+
+SConscript(os.path.join('docs', 'SConscript'),
+           build_dir=os.path.join('mybuild','docs'),
+           duplicate=0)
+""")
+
+
+test.write(['docs', 'SConscript'], """\
+Import('env')
+
+test_dvi = env.DVI(source='test.tex')
+testpdf = env.PDF(source=test_dvi)
+""")
+
+
+test.write(['docs', 'Fig1.ps'], """\
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: Fig1.fig
+%%Creator: fig2dev Version 3.2 Patchlevel 4
+%%CreationDate: Tue Apr 25 09:56:11 2006
+%%BoundingBox: 0 0 98 98
+%%Magnification: 1.0000
+%%EndComments
+/$F2psDict 200 dict def
+$F2psDict begin
+$F2psDict /mtrx matrix put
+/col-1 {0 setgray} bind def
+/col0 {0.000 0.000 0.000 srgb} bind def
+
+end
+save
+newpath 0 98 moveto 0 0 lineto 98 0 lineto 98 98 lineto closepath clip newpath
+-24.9 108.2 translate
+1 -1 scale
+
+/cp {closepath} bind def
+/ef {eofill} bind def
+/gr {grestore} bind def
+/gs {gsave} bind def
+/rs {restore} bind def
+/l {lineto} bind def
+/m {moveto} bind def
+/rm {rmoveto} bind def
+/n {newpath} bind def
+/s {stroke} bind def
+/slc {setlinecap} bind def
+/slj {setlinejoin} bind def
+/slw {setlinewidth} bind def
+/srgb {setrgbcolor} bind def
+/sc {scale} bind def
+/sf {setfont} bind def
+/scf {scalefont} bind def
+/tr {translate} bind def
+ /DrawEllipse {
+       /endangle exch def
+       /startangle exch def
+       /yrad exch def
+       /xrad exch def
+       /y exch def
+       /x exch def
+       /savematrix mtrx currentmatrix def
+       x y tr xrad yrad sc 0 0 1 startangle endangle arc
+       closepath
+       savematrix setmatrix
+       } def
+
+/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def
+/$F2psEnd {$F2psEnteredState restore end} def
+
+$F2psBegin
+10 setmiterlimit
+ 0.06299 0.06299 sc
+%
+% Fig objects follow
+% 
+7.500 slw
+% Ellipse
+n 1170 945 766 766 0 360 DrawEllipse gs col0 s gr
+
+$F2psEnd
+rs
+""")
+
+
+test.write(['docs', 'Fig1.tex'],
+r"""\begin{picture}(0,0)%
+\includegraphics{Fig1.ps}%
+\end{picture}%
+\setlength{\unitlength}{4144sp}%
+%
+\begingroup\makeatletter\ifx\SetFigFont\undefined%
+\gdef\SetFigFont#1#2#3#4#5{%
+  \reset@font\fontsize{#1}{#2pt}%
+  \fontfamily{#3}\fontseries{#4}\fontshape{#5}%
+  \selectfont}%
+\fi\endgroup%
+\begin{picture}(1548,1546)(397,-879)
+\put(856,-196){\makebox(0,0)[lb]{\smash{\SetFigFont{12}{14.4}{\rmdefault}{\mddefault}{\updefault}{\color[rgb]{0,0,0}center $r_0$}%
+}}}
+\end{picture}
+""")
+
+
+test.write(['docs', 'test.bib'], """\
+%% This BibTeX bibliography file was created using BibDesk.
+%% http://bibdesk.sourceforge.net/
+
+%% Saved with string encoding Western (ASCII) 
+
+@techreport{AnAuthor:2006fk,
+       Author = {A. N. Author},
+       Date-Added = {2006-11-15 12:51:30 -0800},
+       Date-Modified = {2006-11-15 12:52:35 -0800},
+       Institution = {none},
+       Month = {November},
+       Title = {A Test Paper},
+       Year = {2006}}
+""")
+
+
+test.write(['docs', 'test.tex'],
+r"""\documentclass{report}
+
+\usepackage{graphicx}
+\usepackage{epsfig,color} % for .tex version of figures if we go that way
+
+\usepackage{makeidx}
+\makeindex
+
+\begin{document}
+\title{Report Title}
+
+\author{A. N. Author}
+\maketitle 
+\begin{abstract}
+there is no abstract
+\end{abstract}
+
+\tableofcontents
+\listoffigures
+
+\chapter{Introduction}
+
+The introduction is short.
+
+\index{Acknowledgements}
+
+\section{Acknowledgements}
+
+The Acknowledgements are show as well \cite{AnAuthor:2006fk}.  
+
+\index{Getting the Report}
+
+To get a hard copy of this report call me.
+
+\begin{figure}[htbp]
+\begin{center}
+\input{Fig1.tex} % testing figure variant that uses TeX labeling
+\caption{Zone and Node indexing}
+\label{fig1}
+\end{center}
+\end{figure}
+
+All done now.
+
+\bibliographystyle{unsrt}
+\bibliography{test}
+\newpage
+
+\printindex
+
+\end{document}
+""")
+
+
+# makeindex will write status messages to stderr (grrr...), so ignore it.
+test.run(arguments = '.', stderr=None)
+
+
+# All (?) the files we expect will get created in the build_dir
+# (mybuild/docs) and not in the srcdir (docs).
+files = [
+    'test.aux',
+    'test.bbl',
+    'test.blg',
+    'test.dvi',
+    'test.idx',
+    'test.ilg',
+    'test.ind',
+    'test.lof',
+    'test.log',
+    'test.pdf',
+    'test.toc',
+]
+
+for f in files:
+    test.must_exist(['mybuild', 'docs', f])
+    test.must_not_exist(['docs', f])
+
+
+test.pass_test()
index d4e2d79855b3872298443ff66e37f4c942d26a54..686263d8cf2f1b0fed578365b422caf9435c14ad 100644 (file)
@@ -28,6 +28,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 Validate that both .tex and .ltx files can handle a LaTeX-style
 bibliography (by calling $BIBTEX to generate a .bbl file) and
 correctly re-run to resolve undefined references.
+
+Also verifies that package warnings are caught and re-run as needed.
 """
 
 import string
@@ -42,7 +44,7 @@ latex = test.where_is('latex')
 if not tex and not latex:
     test.skip_test("Could not find tex or latex; skipping test(s).\n")
 
-test.subdir('work1', 'work2', 'work4')
+test.subdir('work1', 'work2', 'work3', 'work4')
 
 
 input_file = r"""
@@ -64,6 +66,62 @@ Hello world.
 \end{document}
 """
 
+input_file3 = r"""
+\documentclass{article}
+\usepackage{longtable}
+
+\begin{document}
+As stated in the last paper, this is a bug-a-boo.
+here is some more junk and another table
+here is some more junk and another table
+
+\begin{longtable}[l]{rlll}
+         Isotope  &\multicolumn{1}{c}{Abar}  &Name\\
+\\
+         1001    &1.0078    &Proton        &$p$\\
+         1002    &2.0141    &Deuterium     &$d$\\
+         1003    &3.0170    &Tritium       &$t$\\
+         2003    &3.0160    &Helium 3      &He$^3$\\
+         2004    &4.0026    &Helium 4      &He$^{4}$\\
+\end{longtable}
+
+and a closing comment
+
+ These parameters and arrays are filled in when the parameter \textbf{iftnrates}
+   is set to 1:
+
+\begin{longtable}[l]{ll}
+\\
+\textbf{nxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{nxxxxx}    &Total number of particles made by xxxxx reaction\\
+\textbf{nxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{nxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{nxxxxx}    &Total number of particles made by xxxxx reaction\\
+\textbf{nxxxx}     &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx}     &Total number of particles made by xxxx reaction\\
+\\
+\textbf{rnxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rnxxxxx}   &Regional total of particles made by xxxxx reaction\\
+\textbf{rnxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rnxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rnxxxxx}   &Regional total of particles made by xxxxx reaction\\
+\textbf{rnxxxx}    &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx}    &Regional total of particles made by xxxx reaction\\
+\\
+\textbf{reactot}(r)     &Total number of reactions for reaction r\\
+\textbf{reacreg}(r,ir)  &Total number of reactions for reaction r in region ir\\
+\end{longtable}
+
+
+\end{document}
+"""
+
 bibfile = r"""
 @Article{X,
   author =      "Mr. X",
@@ -95,6 +153,20 @@ PDF( "foo.tex" )
         print foo_log
         test.fail_test(1)
 
+    test.write(['work3', 'SConstruct'], """\
+DVI( "foo3.tex" )
+""")
+
+    test.write(['work3', 'foo3.tex'], input_file3)
+
+    test.run(chdir = 'work3', arguments = '.')
+
+    foo_log = test.read(['work3', 'foo3.log'])
+    if string.find(foo_log, 'Rerun LaTeX') != -1:
+        print 'foo.log contains "Rerun LaTeX":'
+        print foo_log
+        test.fail_test(1)
+
 
 
 if latex:
@@ -117,6 +189,20 @@ PDF( "foo.ltx" )
         print foo_log
         test.fail_test(1)
 
+    test.write(['work3', 'SConstruct'], """\
+DVI( "foo3.tex" )
+PDF( "foo3.tex" )
+""")
+
+    test.write(['work3', 'foo3.tex'], input_file3)
+
+    test.run(chdir = 'work3', arguments = '.')
+
+    foo_log = test.read(['work3', 'foo3.log'])
+    if string.find(foo_log, 'Rerun LaTeX') != -1:
+        print 'foo.log contains "Rerun LaTeX":'
+        print foo_log
+        test.fail_test(1)
 
 
     test.write(['work4', 'SConstruct'], """\
index 4934570da0c0f21ce295207dfb22ed0dea64da00..0e5f15622cc7f668c70f5eb0b5d1a89ae1aa5f2a 100644 (file)
@@ -103,13 +103,19 @@ graph:        GRAPH_T
 %%
 """)
 
+import sys
+if sys.platform == 'darwin':
+   file_hpp = 'file.cpp.h'
+else:
+   file_hpp = 'file.hpp'
+
 test.write("hello.cpp", """\
-#include "file.hpp"
+#include "%(file_hpp)s"
 
 int main()
 {
 }
-""")
+""" % locals())
 
 test.write('foo.y', yacc % 'foo.y')
 
diff --git a/test/build-errors.py b/test/build-errors.py
deleted file mode 100644 (file)
index e25fbd5..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-#!/usr/bin/env python
-#
-# __COPYRIGHT__
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-import os
-import string
-import sys
-import TestCmd
-import TestSCons
-
-test = TestSCons.TestSCons()
-
-no_such_file = test.workpath("no_such_file")
-not_executable = test.workpath("not_executable")
-
-test.write(not_executable, "\n")
-
-test.write("f1.in", "\n")
-test.write("f2.in", "\n")
-test.write("f3.in", "\n")
-
-test.write('SConstruct1', r"""
-bld = Builder(action = '%s $SOURCES $TARGET')
-env = Environment(BUILDERS = { 'bld' : bld })
-env.bld(target = 'f1', source = 'f1.in')
-""" % string.replace(no_such_file, '\\', '\\\\'))
-
-test.run(arguments='-f SConstruct1 .',
-         stdout = test.wrap_stdout("%s f1.in f1\n" % no_such_file, error=1),
-         stderr = None,
-         status = 2)
-
-bad_command = """\
-Bad command or file name
-"""
-
-unrecognized = """\
-'%s' is not recognized as an internal or external command,
-operable program or batch file.
-scons: *** [%s] Error 1
-"""
-
-unspecified = """\
-The name specified is not recognized as an
-internal or external command, operable program or batch file.
-scons: *** [%s] Error 1
-"""
-
-not_found_1 = """
-sh: %s: not found
-scons: *** [%s] Error 1
-"""
-
-not_found_2 = """
-sh: %s:  not found
-scons: *** [%s] Error 1
-"""
-
-No_such = """\
-%s: No such file or directory
-scons: *** [%s] Error 127
-"""
-
-cannot_execute = """\
-%s: cannot execute
-scons *** [%s] Error 126
-"""
-
-Permission_denied = """\
-%s: Permission denied
-scons: *** [%s] Error 126
-"""
-
-permission_denied = """\
-%s: permission denied
-scons: *** [%s] Error 126
-"""
-
-is_a_directory = """\
-%s: is a directory
-scons: *** [%s] Error 126
-"""
-
-test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
-if os.name == 'nt':
-    errs = [
-        bad_command,
-        unrecognized % (no_such_file, 'f1'),
-        unspecified % 'f1'
-    ]
-    test.fail_test(not test.stderr() in errs)
-else:
-    errs = [
-        not_found_1 % (no_such_file, 'f1'),
-        not_found_2 % (no_such_file, 'f1'),
-        No_such % (no_such_file, 'f1'),
-    ]
-    error_message_not_found = 1
-    for err in errs:
-        if string.find(test.stderr(), err) != -1:
-            error_message_not_found = None
-            break
-    test.fail_test(error_message_not_found)
-
-test.write('SConstruct2', r"""
-bld = Builder(action = '%s $SOURCES $TARGET')
-env = Environment(BUILDERS = { 'bld': bld })
-env.bld(target = 'f2', source = 'f2.in')
-""" % string.replace(not_executable, '\\', '\\\\'))
-
-test.run(arguments='-f SConstruct2 .',
-         stdout = test.wrap_stdout("%s f2.in f2\n" % not_executable, error=1),
-         stderr = None,
-         status = 2)
-
-test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
-if os.name == 'nt':
-    errs = [
-        bad_command,
-        unrecognized % (not_executable, 'f2'),
-        unspecified % 'f2'
-    ]
-    test.fail_test(not test.stderr() in errs)
-else:
-    errs = [
-        cannot_execute % (not_executable, 'f2'),
-        Permission_denied % (not_executable, 'f2'),
-        permission_denied % (not_executable, 'f2'),
-    ]
-    error_message_not_found = 1
-    for err in errs:
-        if string.find(test.stderr(), err) != -1:
-            error_message_not_found = None
-            break
-    test.fail_test(error_message_not_found)
-
-test.write('SConstruct3', r"""
-bld = Builder(action = '%s $SOURCES $TARGET')
-env = Environment(BUILDERS = { 'bld' : bld })
-env.bld(target = 'f3', source = 'f3.in')
-""" % string.replace(test.workdir, '\\', '\\\\'))
-
-test.run(arguments='-f SConstruct3 .',
-         stdout = test.wrap_stdout("%s f3.in f3\n" % test.workdir, error=1),
-         stderr = None,
-         status = 2)
-
-test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
-if os.name == 'nt':
-    errs = [
-        bad_command,
-        unrecognized % (test.workdir, 'f3'),
-        unspecified % 'f3'
-    ]
-    test.fail_test(not test.stderr() in errs)
-else:
-    errs = [
-        cannot_execute % (not_executable, 'f3'),
-        is_a_directory % (test.workdir, 'f3'),
-    ]
-    error_message_not_found = 1
-    for err in errs:
-        if string.find(test.stderr(), err) != -1:
-            error_message_not_found = None
-            break
-    test.fail_test(error_message_not_found)
-
-test.write('SConstruct4', r"""
-env = Environment()
-env.Command('test.out', 'test.in', Copy('$TARGET', '$SOURCE'))
-env.InstallAs('test2.out', 'test.out')
-# Mark test2.out as precious so we'll handle the exception in
-# FunctionAction() rather than when the target is cleaned before building.
-env.Precious('test2.out')
-env.Default('test2.out')
-""")
-
-test.write('test.in', "test.in 1\n")
-
-test.run(arguments = '-f SConstruct4 .')
-
-test.write('test.in', "test.in 2\n")
-
-test.writable('test2.out', 0)
-f = open(test.workpath('test2.out'))
-
-test.run(arguments = '-f SConstruct4 .',
-         stderr = None,
-         status = 2)
-
-f.close()
-test.writable('test2.out', 1)
-
-test.description_set("Incorrect STDERR:\n%s" % test.stderr())
-errs = [
-    "scons: *** [test2.out] test2.out: Permission denied\n",
-    "scons: *** [test2.out] test2.out: permission denied\n",
-]
-test.fail_test(test.stderr() not in errs)
-
-test.pass_test()
index 8c7c4ab78f36df0c2a00e7e28a5c083276178b80..28c9a0ae5cf05a123a4d1a234a4e3c0b20a475e1 100644 (file)
@@ -145,10 +145,10 @@ test.fail_test(string.find(err, 'Exception') != -1 or \
 
 # Test bad shell ('./one' is a dir, so it can't be used as a shell).
 # This will also give an exit status not in exitvalmap,
-# with error "Permission denied".
+# with error "Permission denied" or "No such file or directory".
 test.write('SConstruct', """
 env=Environment()
-if env['PLATFORM'] == 'posix':
+if env['PLATFORM'] in ('posix', 'darwin'):
     from SCons.Platform.posix import fork_spawn
     env['SPAWN'] = fork_spawn
 env['SHELL'] = 'one'
@@ -157,10 +157,14 @@ env.Command(target='badshell.out', source=[], action='foo')
 
 test.run(status=2, stderr=None)
 err = test.stderr()
-test.fail_test(string.find(err, 'Exception') != -1 or \
-               string.find(err, 'Traceback') != -1)
-test.fail_test(string.find(err, "ermission") == -1 and \
-              string.find(err, "such file") == -1)
+if string.find(err, 'Exception') != -1 or string.find(err, 'Traceback') != -1:
+    print "Exception or Traceback found in the following error output:"
+    print err
+    test.fail_test()
+if string.find(err, 'ermission') == -1 and string.find(err, 'such file') == -1:
+    print "Missing '[Pp]ermission' or 'such file' string in the following error output:"
+    print err
+    test.fail_test()
 
 
 # Test command with exit status -1.
index 9c5d3afbea318841c4b21f24c4a1441ba80ff08a..85948f9ae7ac26aa607232eee4a5988acccf28f3 100644 (file)
@@ -96,6 +96,7 @@ tools = [
     'g77',
     'gas',
     'gcc',
+    'gfortran',
     'gnulink',
     'gs',
     'hpc++',
index 0a46606a81b9762d0b1a4df0e473e1e65263d9a6..4a460a2b2fec5efdfea3cf33da93b467d7e18eb9 100644 (file)
@@ -32,7 +32,7 @@ _python_ = TestSCons._python_
 
 test = TestSCons.TestSCons()
 
-test.subdir('work1', 'work2')
+test.subdir('work1', 'work2', 'work3')
 
 
 
@@ -49,6 +49,11 @@ import sys
 sys.exit(1)
 """)
 
+
+#
+# Test: work1
+# 
+
 test.write(['work1', 'SConstruct'], """\
 Succeed = Builder(action = r'%(_python_)s ../succeed.py $TARGETS')
 Fail = Builder(action = r'%(_python_)s ../fail.py $TARGETS')
@@ -90,7 +95,27 @@ test.must_not_exist(test.workpath('work1', 'aaa.1'))
 test.must_not_exist(test.workpath('work1', 'aaa.out'))
 test.must_match(['work1', 'bbb.out'], "succeed.py: bbb.out\n")
 
+expect = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Cleaning targets ...
+Removed bbb.out
+scons: done cleaning targets.
+"""
+
+test.run(chdir = 'work1',
+         arguments = '--clean --keep-going aaa.out bbb.out',
+         stdout = expect)
+
+test.must_not_exist(test.workpath('work1', 'aaa.1'))
+test.must_not_exist(test.workpath('work1', 'aaa.out'))
+test.must_not_exist(test.workpath('work1', 'bbb.out'))
+
+
 
+#
+# Test: work2
+# 
 
 test.write(['work2', 'SConstruct'], """\
 Succeed = Builder(action = r'%(_python_)s ../succeed.py $TARGETS')
@@ -126,4 +151,145 @@ test.must_match(['work2', 'ddd.out'], "succeed.py: ddd.out\n")
 
 
 
+#
+# Test: work3
+#
+# Check that the -k (keep-going) switch works correctly when the Nodes
+# forms a DAG. The test case is the following
+#
+#               all
+#                |
+#          +-----+-----+-------------+
+#          |           |             |
+#         a1           a2           a3
+#          |           |             |
+#          +       +---+---+     +---+---+ 
+#          \       |      /      |       |
+#           \   bbb.out  /      a4    ccc.out
+#            \          /       /        
+#             \        /       /  
+#              \      /       /  
+#              aaa.out (fails)
+#
+
+test.write(['work3', 'SConstruct'], """\
+Succeed = Builder(action = r'%(_python_)s ../succeed.py $TARGETS')
+Fail = Builder(action = r'%(_python_)s ../fail.py $TARGETS')
+env = Environment(BUILDERS = { 'Succeed' : Succeed, 'Fail' : Fail })
+a = env.Fail('aaa.out', 'aaa.in')
+b = env.Succeed('bbb.out', 'bbb.in')
+c = env.Succeed('ccc.out', 'ccc.in')
+
+a1 = Alias( 'a1', a )
+a2 = Alias( 'a2', a+b) 
+a4 = Alias( 'a4', c) 
+a3 = Alias( 'a3', a4+c) 
+
+Alias('all', a1+a2+a3)
+""" % locals())
+
+test.write(['work3', 'aaa.in'], "aaa.in\n")
+test.write(['work3', 'bbb.in'], "bbb.in\n")
+test.write(['work3', 'ccc.in'], "ccc.in\n")
+
+
+# Test tegular build (i.e. without -k)
+test.run(chdir = 'work3',
+         arguments = '.',
+         status = 2,
+         stderr = None,
+         stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+scons: building terminated because of errors.
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+test.run(chdir = 'work3',
+         arguments = '-c .')
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+# Current directory
+test.run(chdir = 'work3',
+         arguments = '-k .',
+         status = 2,
+         stderr = None,
+         stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+%(_python_)s ../succeed.py bbb.out
+%(_python_)s ../succeed.py ccc.out
+scons: done building targets (errors occurred during build).
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_exist(['work3', 'bbb.out'])
+test.must_exist(['work3', 'ccc.out'])
+
+
+test.run(chdir = 'work3',
+         arguments = '-c .')
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+# Single target
+test.run(chdir = 'work3',
+         arguments = '--keep-going all',
+         status = 2,
+         stderr = None,
+         stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+%(_python_)s ../succeed.py bbb.out
+%(_python_)s ../succeed.py ccc.out
+scons: done building targets (errors occurred during build).
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_exist(['work3', 'bbb.out'])
+test.must_exist(['work3', 'ccc.out'])
+
+
+test.run(chdir = 'work3',
+         arguments = '-c .')
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+# Separate top-level targets
+test.run(chdir = 'work3',
+         arguments = '-k a1 a2 a3',
+         status = 2,
+         stderr = None,
+         stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+%(_python_)s ../succeed.py bbb.out
+%(_python_)s ../succeed.py ccc.out
+scons: done building targets (errors occurred during build).
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_exist(['work3', 'bbb.out'])
+test.must_exist(['work3', 'ccc.out'])
+
+
 test.pass_test()
index 172cbb089d4b98a20b81dd327e846cc63ca1261b..70857e6637ac65dd68bc978a8a568e1da9a1efab 100644 (file)
@@ -79,7 +79,14 @@ includes = """
     +-bar.h
 """
 test.run(arguments = "--debug=includes foo.ooo")
-test.fail_test(string.find(test.stdout(), includes) == -1)
+
+if string.find(test.stdout(), includes) == -1:
+    print "Did not find expected string in standard output."
+    print "Expected =========================================================="
+    print includes
+    print "Actual ============================================================"
+    print test.stdout()
+    test.fail_test()
 
 # In an ideal world, --debug=includes would also work when there's a build
 # failure, but this would require even more complicated logic to scan
index 8c8b2cf29ab35759b6c27b24d893f228ab822432..ad35b5d5497f53fd4295da8460a86faaa3ec1224 100644 (file)
@@ -29,6 +29,7 @@ Test calling the --debug=memoizer option.
 """
 
 import os
+import new
 import string
 
 import TestSCons
@@ -39,15 +40,22 @@ test = TestSCons.TestSCons(match = TestSCons.match_re)
 
 class M:
     def __init__(cls, name, bases, cls_dict):
-        cls.has_metaclass = 1
-
-class A:
-    __metaclass__ = M
+        cls.use_metaclass = 1
+        def fake_method(self):
+            pass
+        new.instancemethod(fake_method, None, cls)
 
 try:
-    has_metaclass = A.has_metaclass
+    class A:
+        __metaclass__ = M
+
+    use_metaclass = A.use_metaclass
 except AttributeError:
-    has_metaclass = None
+    use_metaclass = None
+    reason = 'no metaclasses'
+except TypeError:
+    use_metaclass = None
+    reason = 'new.instancemethod\\(\\) bug'
 
 
 
@@ -72,12 +80,8 @@ expect = [
     "Node._children_get()",
 ]
 
-expect_no_metaclasses = """
-scons: warning: memoization is not supported in this version of Python \\(no metaclasses\\)
-""" + TestSCons.file_expr
-
 
-if has_metaclass:
+if use_metaclass:
 
     def run_and_check(test, args, desc):
         test.run(arguments = args)
@@ -92,6 +96,12 @@ if has_metaclass:
 
 else:
 
+    expect_no_metaclasses = """
+scons: warning: memoization is not supported in this version of Python \\(%s\\)
+""" % reason
+
+    expect_no_metaclasses = expect_no_metaclasses + TestSCons.file_expr
+
     def run_and_check(test, args, desc):
         test.run(arguments = args, stderr = expect_no_metaclasses)
         stdout = test.stdout()
diff --git a/test/option/stack-size.py b/test/option/stack-size.py
new file mode 100644 (file)
index 0000000..495f86d
--- /dev/null
@@ -0,0 +1,357 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import string
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+isStackSizeAvailable = False
+try:
+    import threading
+    isStackSizeAvailable = hasattr(threading,'stack_size')
+except ImportError:
+    pass
+
+test.subdir('work1', 'work2')
+
+test.write('build.py', r"""
+import sys
+contents = open(sys.argv[2], 'rb').read()
+file = open(sys.argv[1], 'wb')
+file.write(contents)
+file.close()
+""")
+
+
+test.write(['work1', 'SConstruct'], """
+B = Builder(action = r'%(_python_)s ../build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+f1 = env.B(target = 'f1.out', source = 'f1.in')
+f2 = env.B(target = 'f2.out', source = 'f2.in')
+Requires(f2, f1)
+""" % locals())
+
+test.write(['work1', 'f1.in'], "f1.in\n")
+test.write(['work1', 'f2.in'], "f2.in\n")
+
+
+test.write(['work2', 'SConstruct'], """
+SetOption('stack_size', 128)
+B = Builder(action = r'%(_python_)s ../build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+f1 = env.B(target = 'f1.out', source = 'f1.in')
+f2 = env.B(target = 'f2.out', source = 'f2.in')
+Requires(f2, f1)
+""" % locals())
+
+test.write(['work2', 'f1.in'], "f1.in\n")
+test.write(['work2', 'f2.in'], "f2.in\n")
+
+
+
+expected_stdout = test.wrap_stdout("""\
+%(_python_)s ../build.py f1.out f1.in
+%(_python_)s ../build.py f2.out f2.in
+""" % locals())
+
+re_expected_stdout = string.replace(expected_stdout, '\\', '\\\\')
+
+expect_unsupported = """
+scons: warning: Setting stack size is unsupported by this version of Python:
+    (('module' object|'threading' module) has no attribute 'stack_size'|stack_size)
+File .*
+"""
+
+
+#
+# Test without any options
+#
+test.run(chdir='work1', 
+         arguments = '.',
+         stdout=expected_stdout,
+         stderr='')
+test.must_exist(['work1', 'f1.out'])
+test.must_exist(['work1', 'f2.out'])
+
+test.run(chdir='work1', 
+         arguments = '-c .')
+test.must_not_exist(['work1', 'f1.out'])
+test.must_not_exist(['work1', 'f2.out'])
+
+#
+# Test with -j2
+#
+test.run(chdir='work1', 
+         arguments = '-j2 .',
+         stdout=expected_stdout,
+         stderr='')
+test.must_exist(['work1', 'f1.out'])
+test.must_exist(['work1', 'f2.out'])
+
+test.run(chdir='work1', 
+         arguments = '-j2 -c .')
+test.must_not_exist(['work1', 'f1.out'])
+test.must_not_exist(['work1', 'f2.out'])
+
+
+#
+# Test with --stack-size
+#
+test.run(chdir='work1', 
+         arguments = '--stack-size=128 .',
+         stdout=expected_stdout,
+         stderr='')
+test.must_exist(['work1', 'f1.out'])
+test.must_exist(['work1', 'f2.out'])
+
+test.run(chdir='work1', 
+         arguments = '--stack-size=128 -c .')
+test.must_not_exist(['work1', 'f1.out'])
+test.must_not_exist(['work1', 'f2.out'])
+
+#
+# Test with SetOption('stack_size', 128)
+#
+test.run(chdir='work2', 
+         arguments = '.',
+         stdout=expected_stdout,
+         stderr='')
+test.must_exist(['work2', 'f1.out'])
+test.must_exist(['work2', 'f2.out'])
+
+test.run(chdir='work2', 
+         arguments = '--stack-size=128 -c .')
+test.must_not_exist(['work2', 'f1.out'])
+test.must_not_exist(['work2', 'f2.out'])
+
+if isStackSizeAvailable:
+    #
+    # Test with -j2 --stack-size=128
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128 .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128 -c .')
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2 --stack-size=16
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 .',
+             match=TestSCons.match_re,
+             stdout=re_expected_stdout,
+             stderr="""
+scons: warning: Setting stack size failed:
+    size not valid: 16384 bytes
+File .*
+""")
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 -c .',
+             match=TestSCons.match_re,
+             stderr="""
+scons: warning: Setting stack size failed:
+    size not valid: 16384 bytes
+File .*
+""")
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2 SetOption('stack_size', 128)
+    #
+    test.run(chdir='work2', 
+             arguments = '-j2 .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work2', 'f1.out'])
+    test.must_exist(['work2', 'f2.out'])
+
+    test.run(chdir='work2', 
+             arguments = '-j2  -c .')
+    test.must_not_exist(['work2', 'f1.out'])
+    test.must_not_exist(['work2', 'f2.out'])
+
+    #
+    # Test with -j2 --stack-size=128 --warn=no-stack-size
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128 --warn=no-stack-size .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128  --warn=no-stack-size -c .')
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2 --stack-size=16 --warn=no-stack-size
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 --warn=no-stack-size .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 --warn=no-stack-size -c .')
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2  --warn=no-stack-size SetOption('stack_size', 128) 
+    #
+    test.run(chdir='work2', 
+             arguments = '-j2  --warn=no-stack-size .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work2', 'f1.out'])
+    test.must_exist(['work2', 'f2.out'])
+
+    test.run(chdir='work2', 
+             arguments = '-j2   --warn=no-stack-size -c .')
+    test.must_not_exist(['work2', 'f1.out'])
+    test.must_not_exist(['work2', 'f2.out'])
+
+else:
+
+    #
+    # Test with -j2 --stack-size=128
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128 .',
+             match=TestSCons.match_re,
+             stdout=re_expected_stdout,
+             stderr=expect_unsupported)
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128 -c .',
+             match=TestSCons.match_re,
+             stderr=expect_unsupported)
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2 --stack-size=16
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 .',
+             match=TestSCons.match_re,
+             stdout=re_expected_stdout,
+             stderr=expect_unsupported)
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 -c .',
+             match=TestSCons.match_re,
+             stderr=expect_unsupported)
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2 SetOption('stack_size', 128)
+    #
+    test.run(chdir='work2', 
+             arguments = '-j2 .',
+             match=TestSCons.match_re,
+             stdout=re_expected_stdout,
+             stderr=expect_unsupported)
+    test.must_exist(['work2', 'f1.out'])
+    test.must_exist(['work2', 'f2.out'])
+
+    test.run(chdir='work2', 
+             arguments = '-j2  -c .',
+             match=TestSCons.match_re,
+             stderr=expect_unsupported)
+    test.must_not_exist(['work2', 'f1.out'])
+    test.must_not_exist(['work2', 'f2.out'])
+
+    #
+    # Test with -j2 --stack-size=128 --warn=no-stack-size
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128 --warn=no-stack-size .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=128  --warn=no-stack-size -c .')
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2 --stack-size=16 --warn=no-stack-size
+    #
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 --warn=no-stack-size .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work1', 'f1.out'])
+    test.must_exist(['work1', 'f2.out'])
+
+    test.run(chdir='work1', 
+             arguments = '-j2 --stack-size=16 --warn=no-stack-size -c .')
+    test.must_not_exist(['work1', 'f1.out'])
+    test.must_not_exist(['work1', 'f2.out'])
+
+    #
+    # Test with -j2  --warn=no-stack-size SetOption('stack_size', 128) 
+    #
+    test.run(chdir='work2', 
+             arguments = '-j2  --warn=no-stack-size .',
+             stdout=expected_stdout,
+             stderr='')
+    test.must_exist(['work2', 'f1.out'])
+    test.must_exist(['work2', 'f2.out'])
+
+    test.run(chdir='work2', 
+             arguments = '-j2   --warn=no-stack-size -c .')
+    test.must_not_exist(['work2', 'f1.out'])
+    test.must_not_exist(['work2', 'f2.out'])
+
+test.pass_test()
index c9486c4b447b030d3cbd0b0b5f326a3c4a4f286c..079c125e27eb99c740cae19c5bc3c5957c3a4393 100644 (file)
@@ -31,7 +31,7 @@ affect how the func subcommand processes things.
 
 import TestSCons_time
 
-test = TestSCons_time.TestSCons_time(match = TestSCons_time.match_re)
+test = TestSCons_time.TestSCons_time()
 
 try:
     import pstats
@@ -56,22 +56,43 @@ prefix = 'foo-001'
 
 expect1 = r'\d.\d\d\d prof1\.py:1\(_main\)' + '\n'
 
-test.run(arguments = 'func -f st1.conf', stdout = expect1)
+test.run(arguments = 'func -f st1.conf',
+         match = TestSCons_time.match_re,
+         stdout = expect1)
 
 
 test.write('st2.conf', """\
 prefix = 'foo'
 title = 'ST2.CONF TITLE'
+vertical_bars = (
+    ( 1.4, 7, None ),
+    ( 1.5, 7, "label 1.5" ),
+    ( 1.6, 7, "label 1.6" ),
+)
 """)
 
 expect2 = \
 r"""set title "ST2.CONF TITLE"
 set key bottom left
-plot '-' title "Startup" with lines lt 1
+set label 3 "label 1.5" at 0.5,1.5 right
+set label 4 "label 1.6" at 0.6,1.5 right
+plot '-' title "Startup" with lines lt 1, \
+     '-' notitle with lines lt 7, \
+     '-' title "label 1.5" with lines lt 7, \
+     '-' title "label 1.6" with lines lt 7
 # Startup
 1 0.000
 2 0.000
 e
+1.4 0
+1.4 1
+e
+1.5 0
+1.5 1
+e
+1.6 0
+1.6 1
+e
 """
 
 test.run(arguments = 'func --file st2.conf --fmt gnuplot', stdout = expect2)
index a236df7d7c3124dbfda4be58afc1e60e2596fc10..a1e7181286ac55438b895a2d9b928e4357342b1c 100644 (file)
@@ -53,16 +53,23 @@ test.run(arguments = 'mem -f st1.conf', stdout = expect1)
 test.write('st2.conf', """\
 prefix = 'foo'
 title = 'ST2.CONF TITLE'
+vertical_bars = (
+    ( 1.5, 7, None ),
+)
 """)
 
 expect2 = \
 r"""set title "ST2.CONF TITLE"
 set key bottom left
-plot '-' title "Startup" with lines lt 1
+plot '-' title "Startup" with lines lt 1, \
+     '-' notitle with lines lt 7
 # Startup
 1 4000.000
 2 4000.000
 e
+1.5 0
+1.5 4800
+e
 """
 
 test.run(arguments = 'mem --file st2.conf --fmt gnuplot', stdout = expect2)
index c881397383a027f15c8bef7548c8f9e3509bee51..3cf8e742c04e4b8cf26db13bfb93294e789988b7 100644 (file)
@@ -53,16 +53,23 @@ test.run(arguments = 'obj -f st1.conf Node.FS.Base', stdout = expect1)
 test.write('st2.conf', """\
 prefix = 'foo'
 title = 'ST2.CONF TITLE'
+vertical_bars = (
+    ( 1.5, 7, None ),
+)
 """)
 
 expect2 = \
 r"""set title "ST2.CONF TITLE"
 set key bottom left
-plot '-' title "Startup" with lines lt 1
+plot '-' title "Startup" with lines lt 1, \
+     '-' notitle with lines lt 7
 # Startup
 1 16040.000
 2 16040.000
 e
+1.5 0
+1.5 18000
+e
 """
 
 test.run(arguments = 'obj --file st2.conf --fmt gnuplot Node.FS.Base', stdout = expect2)
index 8f378cbd5477e7089068946df758f545eb880e5d..641f1291addb1288e104813b95b1bbb295aafa7e 100644 (file)
@@ -59,22 +59,9 @@ test.must_exist('foo-329-0.log',
                 'foo-329-2.log',
                 'foo-329-2.prof')
 
-def tempdir_re(*args):
-    import os
-    import os.path
-    import string
-    import tempfile
-
-    sep = re.escape(os.sep)
-    args = (tempfile.gettempdir(), 'scons-time-aegis-',) + args
-    x = apply(os.path.join, args)
-    x = re.escape(x)
-    x = string.replace(x, 'aegis\\-', 'aegis\\-[^%s]*' % sep)
-    return x
-
 expect = [
-    tempdir_re('src', 'script', 'scons.py'),
-    'SCONS_LIB_DIR = %s' % tempdir_re('src', 'engine'),
+    test.tempdir_re('src', 'script', 'scons.py'),
+    'SCONS_LIB_DIR = %s' % test.tempdir_re('src', 'engine'),
 ]
 
 content = test.read(test.workpath('foo-321-2.log'))
index 8d48d2627898f76e2b505889c897ef7571158879..8ddde3d08ec8c130848ca03147bfb21e38846e23 100644 (file)
@@ -38,9 +38,11 @@ test.write_fake_scons_py()
 test.write_sample_project('foo.tar.gz')
 
 test.write('config', """\
-archive_list = ['foo.tar.gz']
+archive_list = ['foo.tar.gz', 'foo-file']
 """)
 
+test.write('foo-file', "foo-file\n")
+
 test.run(arguments = 'run -f config')
 
 test.must_exist('foo-000-0.log',
@@ -50,6 +52,8 @@ test.must_exist('foo-000-0.log',
                 'foo-000-2.log',
                 'foo-000-2.prof')
 
+test.must_exist('foo-file')
+
 
 test.write_sample_project('bar.tar.gz')
 
index f5a3d8c0e77eade7920874bf13befee48d57a132..453829cbc167cbbab3d47718da00b63eaec50583 100644 (file)
@@ -37,28 +37,11 @@ python = TestSCons_time.python
 test = TestSCons_time.TestSCons_time(match = TestSCons_time.match_re)
 test.diff_function = TestSCons_time.diff_re
 
-
-def tempdir_re(*args):
-    import os,sys
-    import os.path
-    import string
-    import tempfile
-
-    sep = re.escape(os.sep)
-    args = (tempfile.gettempdir(), 'scons-time-',) + args
-    x = apply(os.path.join, args)
-    x = re.escape(x)
-    x = string.replace(x, 'time\\-', 'time\\-[^%s]*' % sep)
-    if sys.platform=='darwin':
-        # OSX has /tmp in /private/tmp.
-        x = '(/private)?' + x
-    return x
-
 scons_py = re.escape(test.workpath('src', 'script', 'scons.py'))
 src_engine = re.escape(test.workpath('src', 'engine'))
 
-tmp_scons_time = tempdir_re()
-tmp_scons_time_foo = tempdir_re('foo')
+tmp_scons_time = test.tempdir_re()
+tmp_scons_time_foo = test.tempdir_re('foo')
 
 
 test.write_fake_scons_py()
index fb95dab5a3af039fc567104be2dafe61775b6743..935e2a9621f18592f9cf31a69382a44569796ba7 100644 (file)
@@ -38,28 +38,11 @@ _python_ = re.escape(TestSCons_time._python_)
 test = TestSCons_time.TestSCons_time(match = TestSCons_time.match_re)
 test.diff_function = TestSCons_time.diff_re
 
-
-def tempdir_re(*args):
-    import os,sys
-    import os.path
-    import string
-    import tempfile
-
-    sep = re.escape(os.sep)
-    args = (tempfile.gettempdir(), 'scons-time-',) + args
-    x = apply(os.path.join, args)
-    x = re.escape(x)
-    x = string.replace(x, 'time\\-', 'time\\-[^%s]*' % sep)
-    if sys.platform=='darwin':
-        # OSX has /tmp in /private/tmp.
-        x = '(/private)?' + x
-    return x
-
 scons_py = re.escape(test.workpath('src', 'script', 'scons.py'))
 src_engine = re.escape(test.workpath('src', 'engine'))
 
-tmp_scons_time = tempdir_re()
-tmp_scons_time_foo = tempdir_re('foo')
+tmp_scons_time = test.tempdir_re()
+tmp_scons_time_foo = test.tempdir_re('foo')
 
 
 test.write_fake_scons_py()
index 3839999e2ab94d0b006b6a0c690cb86b2379092d..757f6dffc8afa0c720a7c1c1a8c486effdfe1b20 100644 (file)
@@ -60,22 +60,9 @@ test.must_exist('foo-716-0.log',
                 'foo-716-2.log',
                 'foo-716-2.prof')
 
-def tempdir_re(*args):
-    import os
-    import os.path
-    import string
-    import tempfile
-
-    sep = re.escape(os.sep)
-    args = (tempfile.gettempdir(), 'scons-time-svn-',) + args
-    x = apply(os.path.join, args)
-    x = re.escape(x)
-    x = string.replace(x, 'svn\\-', 'svn\\-[^%s]*' % sep)
-    return x
-
 expect = [
-    tempdir_re('src', 'script', 'scons.py'),
-    'SCONS_LIB_DIR = %s' % tempdir_re('src', 'engine'),
+    test.tempdir_re('src', 'script', 'scons.py'),
+    'SCONS_LIB_DIR = %s' % test.tempdir_re('src', 'engine'),
 ]
 
 content = test.read(test.workpath('foo-617-2.log'), mode='r')
diff --git a/test/scons-time/time/empty.py b/test/scons-time/time/empty.py
new file mode 100644 (file)
index 0000000..7542bfc
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the time subcommand doesn't fail and prints an appropriate
+error message if a log file is empty.
+"""
+
+import TestSCons_time
+
+test = TestSCons_time.TestSCons_time()
+
+
+header = '       Total  SConscripts        SCons     commands\n'
+
+lines = []
+
+line_fmt    = '   11.123456    22.234567    33.345678    44.456789    %s\n'
+empty_fmt   = '                                                       %s\n'
+
+for i in xrange(9):
+    logfile_name = 'foo-%s-0.log' % i
+    if i == 5:
+        test.write(test.workpath(logfile_name), "")
+        lines.append(empty_fmt % logfile_name)
+    else:
+        test.fake_logfile(logfile_name)
+        lines.append(line_fmt % logfile_name)
+
+expect = [header] + lines
+
+test.run(arguments = 'time foo-*.log',
+         stdout = ''.join(expect),
+         stderr = "file 'foo-5-0.log' has no contents!\n")
+
+expect = """\
+set key bottom left
+plot '-' title "Startup" with lines lt 1
+# Startup
+0 11.123456
+1 11.123456
+2 11.123456
+3 11.123456
+4 11.123456
+6 11.123456
+7 11.123456
+8 11.123456
+e
+"""
+
+stderr = "file 'foo-5-0.log' has no contents!\n"
+
+test.run(arguments = 'time --fmt gnuplot --which total foo-*.log',
+         stdout = expect,
+         stderr = stderr)
+
+expect = """\
+set key bottom left
+plot '-' title "Startup" with lines lt 1
+# Startup
+e
+"""
+
+test.run(arguments = 'time --fmt gnuplot foo-5-0.log',
+         stdout = expect,
+         stderr = stderr)
+
+test.pass_test()
index f4046c9f3ec7d4ff17d28aa64af85642a6d0d313..96bd035bced365e918a913595762c047f7b6ae26 100644 (file)
@@ -53,16 +53,23 @@ test.run(arguments = 'time -f st1.conf', stdout = expect1)
 test.write('st2.conf', """\
 prefix = 'foo'
 title = 'ST2.CONF TITLE'
+vertical_bars = (
+    ( 1.5, 7, None ),
+)
 """)
 
 expect2 = \
 r"""set title "ST2.CONF TITLE"
 set key bottom left
-plot '-' title "Startup" with lines lt 1
+plot '-' title "Startup" with lines lt 1, \
+     '-' notitle with lines lt 7
 # Startup
 1 11.123456
 2 11.123456
 e
+1.5 0
+1.5 12
+e
 """
 
 test.run(arguments = 'time --file st2.conf --fmt gnuplot', stdout = expect2)
diff --git a/test/scons-time/time/no-result.py b/test/scons-time/time/no-result.py
new file mode 100644 (file)
index 0000000..ca345b2
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the time subcommand's --which option doesn't fail, and prints
+an appropriate error message, if a log file doesn't have its specific
+requested results.
+"""
+
+import TestSCons_time
+
+test = TestSCons_time.TestSCons_time()
+
+
+header = """\
+set key bottom left
+plot '-' title "Startup" with lines lt 1
+# Startup
+"""
+
+footer = """\
+e
+"""
+
+line_fmt = "%s 11.123456\n"
+
+lines = []
+
+for i in xrange(9):
+    logfile_name = 'foo-%s-0.log' % i
+    if i == 5:
+        test.write(test.workpath(logfile_name), "NO RESULTS HERE!\n")
+    else:
+        test.fake_logfile(logfile_name)
+        lines.append(line_fmt % i)
+
+expect = [header] + lines + [footer]
+
+stderr = "file 'foo-5-0.log' has no results!\n"
+
+
+test.run(arguments = 'time --fmt gnuplot --which total foo*.log',
+         stdout = ''.join(expect),
+         stderr = stderr)
+
+expect = [header] + [footer]
+
+test.run(arguments = 'time --fmt gnuplot foo-5-0.log',
+         stdout = ''.join(expect),
+         stderr = stderr)
+
+test.pass_test()
index 18be1bccb909ad148e1927e09ef790c8240d097d..31632a9735d5d1a9d0f5cc06862deb263a616499 100644 (file)
@@ -25,7 +25,9 @@
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 """
-Verify that we can trivially subclass our "public" classes.
+Verify that we can trivially subclass our "public" classes.  Also
+verify that we can use a trivial subclass of new-style str classes
+as well as UserString as Builder input.
 """
 
 import TestSCons
@@ -33,6 +35,8 @@ import TestSCons
 test = TestSCons.TestSCons()
 
 test.write('SConstruct', """
+copy_action = Copy('$TARGET', '$SOURCE')
+
 # Some day, we'd probably like people to be able to subclass Action and
 # Builder, but that's going to take some serious class-hackery to turn
 # our factory function into the class itself.
@@ -45,18 +49,31 @@ class my_Scanner(Scanner):
 class my_Environment(Environment):
     pass
 env = my_Environment()
-env.Program('hello', 'hello.c')
-""")
+env.Command('f0.out', 'f0.in', copy_action)
+
+from UserString import UserString
+try:
+    class mystr(str):
+        pass
+except TypeError:
+    class mystr(UserString):
+        pass
 
-test.write('hello.c', """\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[]) {
-    printf("hello.c\\n");
-}
+Command(mystr('f1.out'),        mystr('f1.in'), copy_action)
+Command(UserString('f2.out'),   UserString('f2.in'),    copy_action)
+
+Install(mystr('install'),       mystr('f1.out'))
+Install(mystr('install'),       UserString('f2.out'))
 """)
 
+test.write('f0.in', "f0.in\n")
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
 test.run(arguments = '.')
 
+test.must_match('f0.out',              "f0.in\n")
+test.must_match(['install', 'f1.out'], "f1.in\n")
+test.must_match(['install', 'f2.out'], "f2.in\n")
+
 test.pass_test()
index 9d89d704e7a8c74bbc54d7a6cf8befda538cf23b..0774995171535cce0d6d0380d40a22505e645503 100644 (file)
@@ -79,7 +79,8 @@ test.write('f2.in', "f2.in\n")
 test.write('f3.in', "f3.in\n")
 test.write('f4.in', "f4.in\n")
 
-test.run(arguments = 'f1.out f3.out')
+test.run(arguments = 'f1.out f3.out',
+         stderr = None)
 
 test.run(arguments = 'f1.out f2.out f3.out f4.out',
          stdout = test.wrap_stdout("""\
@@ -87,7 +88,8 @@ scons: `f1.out' is up to date.
 build(["f2.out"], ["f2.in"])
 scons: `f3.out' is up to date.
 build(["f4.out"], ["f4.in"])
-"""))
+"""),
+         stderr = None)
 
 os.utime(test.workpath('f1.in'), 
          (os.path.getatime(test.workpath('f1.in')),
@@ -102,7 +104,8 @@ build(["f1.out"], ["f1.in"])
 scons: `f2.out' is up to date.
 build(["f3.out"], ["f3.in"])
 scons: `f4.out' is up to date.
-"""))
+"""),
+         stderr = None)
 
 
 test.pass_test()
diff --git a/timings/CPPPATH/SConstruct b/timings/CPPPATH/SConstruct
new file mode 100644 (file)
index 0000000..728db9c
--- /dev/null
@@ -0,0 +1,66 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+This configuration is for testing the timing of searching long lists of
+CPPPATH directories.
+
+We create 100 on-disk directories, with a single .h file in the last
+directory in the list.  We set CPPPATH to a list of Dir Nodes for the
+created directories.  The .c file we create #includes the .h file to be
+found in the last directory in the list.
+"""
+
+import os
+import os.path
+
+dir_cnt = 100
+
+dir_list = map(lambda t: 'inc_%03d' % t, xrange(dir_cnt))
+
+for dir in dir_list:
+    if not os.path.isdir(dir):
+        os.mkdir(dir)
+
+foo_h = 'inc_099/foo.h'
+
+if not os.path.isfile(foo_h):
+    open(foo_h, 'w').write('#define     FOO     1\n')
+
+contents = """\
+#include "foo.h"
+void
+foo(void)
+{
+    ;
+}
+"""
+
+if not os.path.isfile('foo.c'):
+    open('foo.c', 'w').write(contents)
+
+inc_list = map(lambda d: Dir(d), dir_list)
+
+env = Environment(CPPPATH = inc_list)
+
+env.Object( 'foo.c' )
diff --git a/timings/CPPPATH/st.conf b/timings/CPPPATH/st.conf
new file mode 100644 (file)
index 0000000..6507ea2
--- /dev/null
@@ -0,0 +1,44 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+scons-time.py configuration file for the "CPPPATH" timing test.
+"""
+
+archive_list = [ 'SConstruct' ]
+subdir = '.'
+
+import sys
+sys.path.insert(0, '..')
+import SCons_Bars
+
+revs = [
+    1224,   # Don't create a Node for every file we try to find during scan.
+    1349,   # More efficient checking for on-disk file entries.
+    1407,   # Use a Dir scanner instead of a hard-coded method.
+    1433,   # Remove unnecessary creation of RCS and SCCS Node.Dir nodes.
+    1703,   # Lobotomize Memoizer.
+    2380,   # The Big Signature Refactoring hits branches/core.
+]
+
+vertical_bars = SCons_Bars.Release_Bars.gnuplot(labels=True) + \
+                SCons_Bars.Revision_Bars.gnuplot(labels=False, revs=revs)
diff --git a/timings/JTimer/SConstruct b/timings/JTimer/SConstruct
new file mode 100644 (file)
index 0000000..e1e38d2
--- /dev/null
@@ -0,0 +1,54 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+This configuration is for timing how we evaluate long chains of
+dependencies, specifically when -j is used.
+
+We set up a chain of 100 targets that get built from a Python function
+action with no source files (equivalent to "echo junk > $TARGET").
+Each target explicitly depends on the next target in turn, so the
+Taskmaster will do a deep walk of the dependency graph.
+
+This test case was contributed by Kevin Massey.  Prior to revision 1468,
+we had a serious O(N^2) problem in the Taskmaster when handling long
+dependency chains like this.  That was fixed by adding reference counts
+to the Taskmaster so it could be smarter about not re-evaluating Nodes.
+"""
+
+target_cnt = 100
+
+env = Environment()
+
+def write_file( env, target, source ):
+    path_target = env.File( target ).path
+    outfile = open( path_target, 'w' )
+    outfile.write( 'junk' )
+    outfile.close()
+
+list = []
+for i in range( target_cnt ):
+    target = 'target_%03d' % i
+    env.Command( target, [], write_file )
+    env.Depends( target, list )
+    list.append( target )
diff --git a/timings/JTimer/st.conf b/timings/JTimer/st.conf
new file mode 100644 (file)
index 0000000..cf1ecbf
--- /dev/null
@@ -0,0 +1,48 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+scons-time.py configuration file for the "JTimer" timing test.
+"""
+
+archive_list = [ 'SConstruct' ]
+subdir = '.'
+targets = '-j2'
+
+import sys
+sys.path.insert(0, '..')
+import SCons_Bars
+
+revs = [
+    1261,   # Fix -j re-scanning built files for implicit deps.
+    1307,   # Move signature Node tranlation of rel_paths into the class.
+    1407,   # Use a Dir scanner instead of a hard-coded method.
+    1435,   # Don't prep .sconsign dependencies until needed.
+    1468,   # Use waiting-Node reference counts to speed up Taskmaster.
+    1703,   # Lobotomize Memoizer.
+    1706,   # Fix _doLookup value-cache misspellings.
+    2380,   # The Big Signature Refactoring hits branches/core.
+]
+
+vertical_bars = SCons_Bars.Release_Bars.gnuplot(labels=True) + \
+                SCons_Bars.Revision_Bars.gnuplot(labels=False, revs=revs)
diff --git a/timings/SCons_Bars.py b/timings/SCons_Bars.py
new file mode 100644 (file)
index 0000000..c56fb36
--- /dev/null
@@ -0,0 +1,118 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+A quick module for central collection of information about which
+Subversion revisions are important for performance implications.
+"""
+
+class Bars(dict):
+    """
+    Dictionary subclass for mapping revision numbers to labels describing
+    each revision.
+
+    We provide two extensions:  a .color attribute (for the default
+    color) and a .gnuplot() method (for returning a list of revisions
+    in the tuple format that scons-time uses to describe vertical bars).
+    """
+    def __init__(self, dict=None, color=None, **kwargs):
+        super(Bars, self).__init__(dict, **kwargs)
+        self.color = color
+    def gnuplot(self, color=None, labels=False, revs=None):
+        if color is None:
+            color = self.color
+        if revs is None:
+            revs = self.keys()
+            revs.sort()
+        if labels:
+            result = [ (r, color, None, self[r]) for r in revs ]
+        else:
+            result = [ (r, color, None, None) for r in revs ]
+        return tuple(result)
+
+# The Release_Bars dictionary records the Subversion revisions that
+# correspond to each official SCons release.
+
+Release_Bars = Bars(
+    color = 7,
+    dict = {
+        1232 : '0.96.90', 
+        1344 : '0.96.91', 
+        1435 : '0.96.92', 
+        1674 : '0.96.93', 
+        1765 : '0.96.94', 
+        1835 : '0.96.95', 
+        1882 : '0.96.96', 
+        1901 : '0.97', 
+        2242 : '0.97.0d20070809', 
+        2454 : '0.97.0d20070918', 
+        2527 : '0.97.0d20071212', 
+    },
+)
+
+
+# The Revisions_Bars dictionary records the Subversion revisions that
+# correspond to "interesting" changes in timing.  This is essentially the
+# global list of interesting changes.  Individual timing configurations
+# typically only display bars for a subset of these, the ones that
+# actually affect their configuration.
+#
+# Note that the default behavior of most of the st.conf files is to
+# *not* display the labels for each of these lines, since they're long
+# and verbose.  So in practice they function as comments describing the
+# changes that have timing impacts on various configurations.
+
+Revision_Bars = Bars(
+    color = 5,
+    dict = {
+        1220 : 'Use WeakValueDicts in the Memoizer to reduce memory use.',
+        1224 : 'Don\'t create a Node for every file we try to find during scan.',
+        1231 : 'Don\'t pick same-named directories in a search path.',
+        1241 : 'Optimize out N*M suffix matching in Builder.py.',
+        1245 : 'Reduce gen_binfo() time for long source lists.',
+        1261 : 'Fix -j re-scanning built files for implicit deps.',
+        1262 : 'Match Entries when searching paths for Files or Dirs.',
+        1273 : 'Store paths in .sconsign relative to target directory.',
+        1282 : 'Cache result from rel_path().',
+        1307 : 'Move signature Node tranlation of rel_paths into the class.',
+        1346 : 'Give subst logic its own module.',
+        1349 : 'More efficient checking for on-disk file entries.',
+        1407 : 'Use a Dir scanner instead of a hard-coded method.',
+        1433 : 'Remove unnecessary creation of RCS and SCCS Node.Dir nodes.',
+        1435 : 'Don\'t convert .sconsign dependencies to Nodes until needed.',
+        1468 : 'Use waiting-Node reference counts to speed up Taskmaster.',
+        1477 : 'Delay disambiguation of Node.FS.Entry into File/Dir.',
+        1533 : 'Fix some disambiguation-delay ramifications.',
+        1655 : 'Reduce unnecessary calls to Node.FS.disambiguate().',
+        1703 : 'Lobotomize Memoizer.',
+        1706 : 'Fix _doLookup value-cache misspellings.',
+        1712 : 'PathList, restore caching of Builder source suffixes.',
+        1724 : 'Cache Node.FS.find_file() and Node.FS.Dir.srcdir_find_file().',
+        1727 : 'Cache Executor methods, reduce calls when scanning.',
+        1752 : 'Don\'t cache Builder source suffixes too early.',
+        1790 : 'Clean up various module imports (pychecker fixes).',
+        1794 : 'Un-fix various later-Python-version pychecker "fixes".',
+        1828 : 'Speed up Builder suffix-matching (SuffixMap).',
+        2380 : 'The Big Signature Refactoring hits branches/core.',
+    },
+)
diff --git a/timings/hundred/SConstruct b/timings/hundred/SConstruct
new file mode 100644 (file)
index 0000000..2332d73
--- /dev/null
@@ -0,0 +1,52 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+This configuration is for timing how we handle the NxM interaction when
+we build a lot of targets from a lot of source files.
+
+We create a list of 100 target files that will each be built by copying
+a file from a corresponding list of 100 source files.  The source
+files themselves are each built by a Python function action that's the
+equivalent of "echo contents > $TARGET".
+"""
+
+target_cnt = 100
+
+env = Environment()
+
+def create_file( env, target, source ):
+    t = str(target[0])
+    open( t, 'w' ).write('contents\n')
+
+source_list = map(lambda t: 'source_%03d' % t, xrange(target_cnt))
+target_list = map(lambda t: 'target_%03d' % t, xrange(target_cnt))
+
+for source in source_list:
+    env.Command( source, [], create_file )
+
+def copy_files( env, target, source ):
+    for t, s in zip(target, source):
+        open(str(t),  'w').write(open(str(s), 'r').read())
+
+env.Command( target_list, source_list, copy_files )
diff --git a/timings/hundred/st.conf b/timings/hundred/st.conf
new file mode 100644 (file)
index 0000000..adfb09e
--- /dev/null
@@ -0,0 +1,47 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+scons-time.py configuration file for the "hundred" timing test.
+"""
+
+archive_list = [ 'SConstruct' ]
+subdir = '.'
+
+import sys
+sys.path.insert(0, '..')
+import SCons_Bars
+
+revs = [
+    1220,   # Use WeakValueDicts in the Memoizer to reduce memory use.
+    1307,   # Move signature Node tranlation of rel_paths into the class.
+    1435,   # Fix Debug.caller() directory separators.
+    1477,   # Delay disambiguation of Node.FS.Entry into File/Dir.
+    1655,   # Reduce unnecessary calls to Node.FS.disambiguate().
+    1703,   # Lobotomize Memoizer.
+    1727,   # Cache Executor methods, reduce calls when scanning.
+    2380,   # The Big Signature Refactoring hits branches/core.
+]
+
+vertical_bars = SCons_Bars.Release_Bars.gnuplot(labels=True) + \
+                SCons_Bars.Revision_Bars.gnuplot(labels=False, revs=revs)