From: stevenknight Date: Mon, 4 Feb 2008 19:07:24 +0000 (+0000) Subject: Merged revisions 2527-2645 via svnmerge from X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=256a41aeaf85783be2c92fd6b9327ade28488ce4;p=scons.git Merged revisions 2527-2645 via svnmerge from 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 ("") 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). ........ 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 --- diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index f5e1c715..8bf054b3 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -181,12 +181,12 @@ version. # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight " -__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. diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py index d6b21adf..acc63d49 100644 --- a/QMTest/TestCommon.py +++ b/QMTest/TestCommon.py @@ -84,9 +84,10 @@ The TestCommon module also provides the following variables # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight " -__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. diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index b1fdbc1c..6b6f5ed7 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -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 [ ]', 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), diff --git a/QMTest/TestSCons_time.py b/QMTest/TestSCons_time.py index 102181e4..f3ea49a9 100644 --- a/QMTest/TestSCons_time.py +++ b/QMTest/TestSCons_time.py @@ -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) diff --git a/doc/man/scons-time.1 b/doc/man/scons-time.1 index b2de0029..07832a99 100644 --- a/doc/man/scons-time.1 +++ b/doc/man/scons-time.1 @@ -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 diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 526a5a3c..ae252747 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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. diff --git a/doc/scons.mod b/doc/scons.mod index c23e6ae0..e590368d 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -22,6 +22,7 @@ as"> Autoconf"> Automake"> +bison"> cc"> Cons"> cp"> diff --git a/src/CHANGES.txt b/src/CHANGES.txt index c8dc7d3f..81b54e65 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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: diff --git a/src/RELEASE.txt b/src/RELEASE.txt index ca01607b..7327f21b 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -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 diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 52323d20..093fbd95 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -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 diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index c2c11581..cd4bf6aa 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -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 [] diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 06030e3b..2ad4bef7 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -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 diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py index 9b2b4b4b..7caee618 100644 --- a/src/engine/SCons/CacheDir.py +++ b/src/engine/SCons/CacheDir.py @@ -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 diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py index fcf8c5a7..33899f60 100644 --- a/src/engine/SCons/Conftest.py +++ b/src/engine/SCons/Conftest.py @@ -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): """ diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index c3d30cb2..3cd47efe 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -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) diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index cf2d0eb3..02ad3324 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -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 diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 3f64d43c..4ffff7ad 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -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""" diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index 1cb0cf97..72220424 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -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 diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py index b28aaaff..7b514092 100644 --- a/src/engine/SCons/Job.py +++ b/src/engine/SCons/Job.py @@ -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 diff --git a/src/engine/SCons/JobTests.py b/src/engine/SCons/JobTests.py index 5f056e86..c4325812 100644 --- a/src/engine/SCons/JobTests.py +++ b/src/engine/SCons/JobTests.py @@ -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 diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py index c2b41818..c4a5001e 100644 --- a/src/engine/SCons/Memoize.py +++ b/src/engine/SCons/Memoize.py @@ -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: diff --git a/src/engine/SCons/MemoizeTests.py b/src/engine/SCons/MemoizeTests.py index 7102f305..bceeebf8 100644 --- a/src/engine/SCons/MemoizeTests.py +++ b/src/engine/SCons/MemoizeTests.py @@ -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: diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index d0843d1f..1a3c0100 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -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: diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index fe42035d..8e9a3f8d 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -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 diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index f2521515..4ca34e01 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -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): diff --git a/src/engine/SCons/PathList.py b/src/engine/SCons/PathList.py index be645ca2..ae00fc0d 100644 --- a/src/engine/SCons/PathList.py +++ b/src/engine/SCons/PathList.py @@ -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: diff --git a/src/engine/SCons/Platform/posix.py b/src/engine/SCons/Platform/posix.py index 1d4e9f70..afdabe1a 100644 --- a/src/engine/SCons/Platform/posix.py +++ b/src/engine/SCons/Platform/posix.py @@ -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 diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index ae3a77eb..c5de4988 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -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) diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index 601c5eb4..f7d33f8e 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -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 ") + 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() """ diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index 276570e0..4356c7a4 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -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", diff --git a/src/engine/SCons/Scanner/D.py b/src/engine/SCons/Scanner/D.py index 5a0b3834..bfbcd5de 100644 --- a/src/engine/SCons/Scanner/D.py +++ b/src/engine/SCons/Scanner/D.py @@ -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 diff --git a/src/engine/SCons/Scanner/LaTeX.py b/src/engine/SCons/Scanner/LaTeX.py index c0a38b56..ceb9bf52 100644 --- a/src/engine/SCons/Scanner/LaTeX.py +++ b/src/engine/SCons/Scanner/LaTeX.py @@ -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.) """ diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py index 64d6d77d..6e9286a4 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -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 diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index c8ab1557..924b2716 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -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 index 00000000..e38c4002 --- /dev/null +++ b/src/engine/SCons/Script/Interactive.py @@ -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_() 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() diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 97e0b190..bcbd0a14 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -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:] diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py index 46ece273..8f7116de 100644 --- a/src/engine/SCons/Script/SConsOptions.py +++ b/src/engine/SCons/Script/SConsOptions.py @@ -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, diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py index 989f1dd5..7a565baa 100644 --- a/src/engine/SCons/Subst.py +++ b/src/engine/SCons/Subst.py @@ -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) diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py index b6e5b71d..c0641641 100644 --- a/src/engine/SCons/SubstTests.py +++ b/src/engine/SCons/SubstTests.py @@ -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']], diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 3bb4225d..9db8138c 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -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 diff --git a/src/engine/SCons/Tool/applelink.py b/src/engine/SCons/Tool/applelink.py index a65a4aff..532301f6 100644 --- a/src/engine/SCons/Tool/applelink.py +++ b/src/engine/SCons/Tool/applelink.py @@ -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 index 00000000..f3db6930 --- /dev/null +++ b/src/engine/SCons/Tool/gfortran.py @@ -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 index 00000000..ba0fe76a --- /dev/null +++ b/src/engine/SCons/Tool/gfortran.xml @@ -0,0 +1,15 @@ + + + +Sets construction variables for the GNU F95/F2003 GNU compiler. + + +FORTRAN +SHFORTRANFLAGS + + diff --git a/src/engine/SCons/Tool/intelc.py b/src/engine/SCons/Tool/intelc.py index 673c8486..02cc52af 100644 --- a/src/engine/SCons/Tool/intelc.py +++ b/src/engine/SCons/Tool/intelc.py @@ -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 diff --git a/src/engine/SCons/Tool/jar.py b/src/engine/SCons/Tool/jar.py index 4f221c04..6594ecc5 100644 --- a/src/engine/SCons/Tool/jar.py +++ b/src/engine/SCons/Tool/jar.py @@ -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 diff --git a/src/engine/SCons/Tool/jar.xml b/src/engine/SCons/Tool/jar.xml index a0d730e8..9e8fefac 100644 --- a/src/engine/SCons/Tool/jar.xml +++ b/src/engine/SCons/Tool/jar.xml @@ -34,6 +34,11 @@ If the &cv-link-JARCHDIR; value is set, the command will change to the specified directory using the option. +If &cv-JARCHDIR; is not set explicitly, +&SCons; will use the top of any subdirectory tree +in which Java .class +were built by the &b-link-Java; Builder. + If the contents any of the source files begin with the string Manifest-Version, the file is assumed to be a manifest diff --git a/src/engine/SCons/Tool/link.py b/src/engine/SCons/Tool/link.py index be1a81aa..b60aa875 100644 --- a/src/engine/SCons/Tool/link.py +++ b/src/engine/SCons/Tool/link.py @@ -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']='' diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py index 25f35646..42eabafb 100644 --- a/src/engine/SCons/Tool/mslink.py +++ b/src/engine/SCons/Tool/mslink.py @@ -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']: diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py index 105f42e5..d67cddb7 100644 --- a/src/engine/SCons/Tool/qt.py +++ b/src/engine/SCons/Tool/qt.py @@ -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( diff --git a/src/engine/SCons/Tool/rmic.py b/src/engine/SCons/Tool/rmic.py index 4b48e0b9..ed5c8ee7 100644 --- a/src/engine/SCons/Tool/rmic.py +++ b/src/engine/SCons/Tool/rmic.py @@ -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) diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py index 8ca1b89c..eba49a77 100644 --- a/src/engine/SCons/Tool/swig.py +++ b/src/engine/SCons/Tool/swig.py @@ -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) diff --git a/src/engine/SCons/Tool/tex.py b/src/engine/SCons/Tool/tex.py index bbae25e0..c3156a3a 100644 --- a/src/engine/SCons/Tool/tex.py +++ b/src/engine/SCons/Tool/tex.py @@ -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) diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py index 34f60cb6..0b648e81 100644 --- a/src/engine/SCons/Tool/yacc.py +++ b/src/engine/SCons/Tool/yacc.py @@ -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): diff --git a/src/engine/SCons/Tool/yacc.xml b/src/engine/SCons/Tool/yacc.xml index 2db06030..aa648b1e 100644 --- a/src/engine/SCons/Tool/yacc.xml +++ b/src/engine/SCons/Tool/yacc.xml @@ -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 -.hpp. +.hpp, +except on Mac OS X, +where the default is +${TARGET.suffix}.h. +because the default &bison; parser generator just +appends .h +to the name of the generated C++ file. diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 258de0f1..08ce1f2a 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -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)) diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 3e8085ba..44d6fa89 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -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("") diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py index b1d39ec6..53549595 100644 --- a/src/engine/SCons/Warnings.py +++ b/src/engine/SCons/Warnings.py @@ -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. diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py index 47ae3bea..91e3776b 100644 --- a/src/engine/SCons/compat/__init__.py +++ b/src/engine/SCons/compat/__init__.py @@ -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 index 00000000..d6c10357 --- /dev/null +++ b/src/engine/SCons/compat/_scons_shlex.py @@ -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 diff --git a/src/engine/SCons/cpp.py b/src/engine/SCons/cpp.py index 8620936f..cdd6a3a9 100644 --- a/src/engine/SCons/cpp.py +++ b/src/engine/SCons/cpp.py @@ -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 diff --git a/src/engine/SCons/cppTests.py b/src/engine/SCons/cppTests.py index 0959e2cc..33fd01d7 100644 --- a/src/engine/SCons/cppTests.py +++ b/src/engine/SCons/cppTests.py @@ -23,10 +23,10 @@ __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 +#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 + """) + 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 + """) + 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) - diff --git a/src/script/scons-time.py b/src/script/scons-time.py index 284443a4..d651ba92 100644 --- a/src/script/scons-time.py +++ b/src/script/scons-time.py @@ -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), diff --git a/src/test_strings.py b/src/test_strings.py index 0d6e6ac6..c446cad0 100644 --- a/src/test_strings.py +++ b/src/test_strings.py @@ -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 index 00000000..00aa688b --- /dev/null +++ b/test/Actions/function.py @@ -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 index 00000000..5c302c33 --- /dev/null +++ b/test/CPPDEFINES/basic.py @@ -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() diff --git a/test/CPPDEFINES.py b/test/CPPDEFINES/live.py similarity index 67% rename from test/CPPDEFINES.py rename to test/CPPDEFINES/live.py index c38f857b..7a169e8b 100644 --- a/test/CPPDEFINES.py +++ b/test/CPPDEFINES/live.py @@ -25,56 +25,13 @@ __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 index 00000000..c9b60c3f --- /dev/null +++ b/test/CPPDEFINES/scan.py @@ -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 +#endif +#if F2 +#include +#endif +#ifdef F3 +#include +#endif +#ifdef F4 +#include +#endif + +char * +foo(void) +{ + return (STRING); +} +""") + + +test.write('prog.c', r""" +#include +#include + +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 index 00000000..b6b8b445 --- /dev/null +++ b/test/CPPDEFINES/undefined.py @@ -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 index 00000000..9adb206d --- /dev/null +++ b/test/CPPPATH/absolute-path.py @@ -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 +#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 index 00000000..7c80f22c --- /dev/null +++ b/test/CPPPATH/function-expansion.py @@ -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 +#include +#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 index 00000000..7e5326f1 --- /dev/null +++ b/test/CPPPATH/list-expansion.py @@ -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 +#include +#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() diff --git a/test/Configure/Builder-call.py b/test/Configure/Builder-call.py index a6f2fa5c..1ce114c6 100644 --- a/test/Configure/Builder-call.py +++ b/test/Configure/Builder-call.py @@ -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()) diff --git a/test/DMD.py b/test/D/DMD.py 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 index 00000000..5c698204 --- /dev/null +++ b/test/D/Scanner.py @@ -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() diff --git a/test/Errors/SyntaxError.py b/test/Errors/SyntaxError.py index b9ff1ffd..956caa7d 100644 --- a/test/Errors/SyntaxError.py +++ b/test/Errors/SyntaxError.py @@ -37,8 +37,10 @@ test.write('SConstruct', """ a ! x """) +# It looks like vanilla Python 2.2 is the only version that +# puts "" here in place of the file name. test.run(stdout = "scons: Reading SConscript files ...\n", - stderr = """ File ".+SConstruct", line 2 + stderr = """ File "(.+SConstruct|)", line 2 a ! x diff --git a/test/Errors/execute-a-directory.py b/test/Errors/execute-a-directory.py new file mode 100644 index 00000000..bcdcb7cb --- /dev/null +++ b/test/Errors/execute-a-directory.py @@ -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 index 00000000..d6e018b9 --- /dev/null +++ b/test/Errors/non-executable-file.py @@ -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 index 00000000..b2a9557d --- /dev/null +++ b/test/Errors/nonexistent-executable.py @@ -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 index 00000000..05d1e9d5 --- /dev/null +++ b/test/Errors/permission-denied.py @@ -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() diff --git a/test/Install/option--install-sandbox.py b/test/Install/option--install-sandbox.py index 4cf9310f..38a7915c 100644 --- a/test/Install/option--install-sandbox.py +++ b/test/Install/option--install-sandbox.py @@ -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 index 00000000..fc05b9ab --- /dev/null +++ b/test/Interactive/Alias.py @@ -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 index 00000000..36ebf2fb --- /dev/null +++ b/test/Interactive/Default-None.py @@ -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 index 00000000..d6205e29 --- /dev/null +++ b/test/Interactive/Default.py @@ -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 index 00000000..8c303146 --- /dev/null +++ b/test/Interactive/added-include.py @@ -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 + +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 + +#include + +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 index 00000000..4c78f69c --- /dev/null +++ b/test/Interactive/basic.py @@ -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 index 00000000..dab51596 --- /dev/null +++ b/test/Interactive/cache-debug.py @@ -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 index 00000000..0fb8435c --- /dev/null +++ b/test/Interactive/cache-disable.py @@ -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 index 00000000..8fd3f920 --- /dev/null +++ b/test/Interactive/cache-force.py @@ -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 index 00000000..c1fe4873 --- /dev/null +++ b/test/Interactive/cache-show.py @@ -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 index 00000000..4f4f80de --- /dev/null +++ b/test/Interactive/clean.py @@ -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 index 00000000..df06d5da --- /dev/null +++ b/test/Interactive/exit.py @@ -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 index 00000000..ef4c5783 --- /dev/null +++ b/test/Interactive/help.py @@ -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 index 00000000..7b7aa4b4 --- /dev/null +++ b/test/Interactive/implicit-BuildDir.py @@ -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 + +#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 + +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 index 00000000..4c02fa72 --- /dev/null +++ b/test/Interactive/option--Q.py @@ -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 index 00000000..a2935a70 --- /dev/null +++ b/test/Interactive/option-i.py @@ -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 index 00000000..29bba88b --- /dev/null +++ b/test/Interactive/option-j.py @@ -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 index 00000000..f15605e9 --- /dev/null +++ b/test/Interactive/option-k.py @@ -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 index 00000000..f5ee1e3e --- /dev/null +++ b/test/Interactive/option-n.py @@ -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 index 00000000..167d581f --- /dev/null +++ b/test/Interactive/option-s.py @@ -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 index 00000000..e85b9ea6 --- /dev/null +++ b/test/Interactive/repeat-line.py @@ -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 index 00000000..9d5e8a2d --- /dev/null +++ b/test/Interactive/shell.py @@ -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 index 00000000..23b9ad90 --- /dev/null +++ b/test/Interactive/taskmastertrace.py @@ -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 index 00000000..96e7d4ad --- /dev/null +++ b/test/Interactive/tree.py @@ -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 index 00000000..2b773780 --- /dev/null +++ b/test/Interactive/unknown-command.py @@ -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 index 00000000..84f70e85 --- /dev/null +++ b/test/Interactive/version.py @@ -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() diff --git a/test/Java/JAR.py b/test/Java/JAR.py index f0951c62..ee552f45 100644 --- a/test/Java/JAR.py +++ b/test/Java/JAR.py @@ -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", """\ diff --git a/test/Java/JARCHDIR.py b/test/Java/JARCHDIR.py index f7d9fca1..a3f2ec4f 100644 --- a/test/Java/JARCHDIR.py +++ b/test/Java/JARCHDIR.py @@ -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() diff --git a/test/Java/JARFLAGS.py b/test/Java/JARFLAGS.py index 3939d983..03a222b8 100644 --- a/test/Java/JARFLAGS.py +++ b/test/Java/JARFLAGS.py @@ -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) diff --git a/test/Java/JAVABOOTCLASSPATH.py b/test/Java/JAVABOOTCLASSPATH.py index 7723224f..5962b945 100644 --- a/test/Java/JAVABOOTCLASSPATH.py +++ b/test/Java/JAVABOOTCLASSPATH.py @@ -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'], diff --git a/test/Java/JAVACFLAGS.py b/test/Java/JAVACFLAGS.py index 045fb7b3..e2870549 100644 --- a/test/Java/JAVACFLAGS.py +++ b/test/Java/JAVACFLAGS.py @@ -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') diff --git a/test/Java/JAVACLASSPATH.py b/test/Java/JAVACLASSPATH.py index 0ae7dea5..11f7c2c4 100644 --- a/test/Java/JAVACLASSPATH.py +++ b/test/Java/JAVACLASSPATH.py @@ -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'], diff --git a/test/Java/JAVAH.py b/test/Java/JAVAH.py index ecd3737f..95abd33f 100644 --- a/test/Java/JAVAH.py +++ b/test/Java/JAVAH.py @@ -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') diff --git a/test/Java/JAVASOURCEPATH.py b/test/Java/JAVASOURCEPATH.py index 069e2282..87e90add 100644 --- a/test/Java/JAVASOURCEPATH.py +++ b/test/Java/JAVASOURCEPATH.py @@ -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'], diff --git a/test/Java/Java-1.4.py b/test/Java/Java-1.4.py index 3d3d47ab..3c0e1ed8 100644 --- a/test/Java/Java-1.4.py +++ b/test/Java/Java-1.4.py @@ -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): diff --git a/test/Java/Java-1.5.py b/test/Java/Java-1.5.py index 4ac3d96c..f6d93c7a 100644 --- a/test/Java/Java-1.5.py +++ b/test/Java/Java-1.5.py @@ -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(); } } """) diff --git a/test/Java/Java-1.6.py b/test/Java/Java-1.6.py index f2b629af..5bd8e2f5 100644 --- a/test/Java/Java-1.6.py +++ b/test/Java/Java-1.6.py @@ -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(); } } """) diff --git a/test/Java/RMIC.py b/test/Java/RMIC.py index 2ab1804e..f9721c2f 100644 --- a/test/Java/RMIC.py +++ b/test/Java/RMIC.py @@ -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 = '.') diff --git a/test/Java/RMICCOM.py b/test/Java/RMICCOM.py index ed5e0d67..e8a56553 100644 --- a/test/Java/RMICCOM.py +++ b/test/Java/RMICCOM.py @@ -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') diff --git a/test/Java/RMICCOMSTR.py b/test/Java/RMICCOMSTR.py index 5a451ebd..a92bac53 100644 --- a/test/Java/RMICCOMSTR.py +++ b/test/Java/RMICCOMSTR.py @@ -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') diff --git a/test/Java/multi-step.py b/test/Java/multi-step.py index d185b4d5..9cac7597 100644 --- a/test/Java/multi-step.py +++ b/test/Java/multi-step.py @@ -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 index 00000000..795689c4 --- /dev/null +++ b/test/Java/no-JARCHDIR.py @@ -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() diff --git a/test/Java/source-files.py b/test/Java/source-files.py index 8d2506fb..63d1d92a 100644 --- a/test/Java/source-files.py +++ b/test/Java/source-files.py @@ -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', """ diff --git a/test/Java/swig-dependencies.py b/test/Java/swig-dependencies.py index 8df5e096..5477a2d4 100644 --- a/test/Java/swig-dependencies.py +++ b/test/Java/swig-dependencies.py @@ -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'], diff --git a/test/LIBPATH.py b/test/Libs/LIBPATH.py similarity index 100% rename from test/LIBPATH.py rename to test/Libs/LIBPATH.py diff --git a/test/LIBPREFIX.py b/test/Libs/LIBPREFIX.py similarity index 100% rename from test/LIBPREFIX.py rename to test/Libs/LIBPREFIX.py diff --git a/test/LIBPREFIXES.py b/test/Libs/LIBPREFIXES.py similarity index 100% rename from test/LIBPREFIXES.py rename to test/Libs/LIBPREFIXES.py diff --git a/test/LIBS.py b/test/Libs/LIBS.py similarity index 100% rename from test/LIBS.py rename to test/Libs/LIBS.py diff --git a/test/LIBSUFFIX.py b/test/Libs/LIBSUFFIX.py similarity index 100% rename from test/LIBSUFFIX.py rename to test/Libs/LIBSUFFIX.py diff --git a/test/LIBSUFFIXES.py b/test/Libs/LIBSUFFIXES.py similarity index 100% rename from test/LIBSUFFIXES.py rename to test/Libs/LIBSUFFIXES.py diff --git a/test/Library.py b/test/Libs/Library.py similarity index 100% rename from test/Library.py rename to test/Libs/Library.py diff --git a/test/SHLIBPREFIX.py b/test/Libs/SHLIBPREFIX.py similarity index 100% rename from test/SHLIBPREFIX.py rename to test/Libs/SHLIBPREFIX.py diff --git a/test/SHLIBSUFFIX.py b/test/Libs/SHLIBSUFFIX.py similarity index 100% rename from test/SHLIBSUFFIX.py rename to test/Libs/SHLIBSUFFIX.py diff --git a/test/SharedLibrary.py b/test/Libs/SharedLibrary.py similarity index 100% rename from test/SharedLibrary.py rename to test/Libs/SharedLibrary.py diff --git a/test/Libs/SharedLibraryIxes.py b/test/Libs/SharedLibraryIxes.py new file mode 100644 index 00000000..f9d14711 --- /dev/null +++ b/test/Libs/SharedLibraryIxes.py @@ -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 + +#ifdef _WIN32 +#define EXPORT __declspec( dllexport ) +#else +#define EXPORT +#endif + +EXPORT void +goo(void) +{ + printf("goo.c\n"); +} +""") + +test.write('foo.c', r""" +#include + +#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 + +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() diff --git a/test/LoadableModule.py b/test/LoadableModule.py index 5243fbf4..4a8e1eda 100644 --- a/test/LoadableModule.py +++ b/test/LoadableModule.py @@ -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 index 00000000..00f3ee27 --- /dev/null +++ b/test/MSVC/pdb-manifest.py @@ -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 +#include +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() diff --git a/test/QT/moc-from-header.py b/test/QT/moc-from-header.py index 28781369..99364906 100644 --- a/test/QT/moc-from-header.py +++ b/test/QT/moc-from-header.py @@ -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) diff --git a/test/QT/warnings.py b/test/QT/warnings.py index baa1e6fa..ef94dd6f 100644 --- a/test/QT/warnings.py +++ b/test/QT/warnings.py @@ -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 diff --git a/test/Repository/Java.py b/test/Repository/Java.py index 6e8bbd6b..67ef605d 100644 --- a/test/Repository/Java.py +++ b/test/Repository/Java.py @@ -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") diff --git a/test/Repository/JavaH.py b/test/Repository/JavaH.py index fc879041..258d9bd1 100644 --- a/test/Repository/JavaH.py +++ b/test/Repository/JavaH.py @@ -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 diff --git a/test/Repository/LIBPATH.py b/test/Repository/LIBPATH.py index 9216a8b3..0f80acef 100644 --- a/test/Repository/LIBPATH.py +++ b/test/Repository/LIBPATH.py @@ -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""" diff --git a/test/Repository/RMIC.py b/test/Repository/RMIC.py index ebe6a83d..bf8edffd 100644 --- a/test/Repository/RMIC.py +++ b/test/Repository/RMIC.py @@ -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 index 00000000..0d89ebe9 --- /dev/null +++ b/test/SWIG/module-parens.py @@ -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() diff --git a/test/Scanner/generated.py b/test/Scanner/generated.py index dd2c938c..ef91189f 100644 --- a/test/Scanner/generated.py +++ b/test/Scanner/generated.py @@ -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 index 00000000..8035957b --- /dev/null +++ b/test/TEX/build_dir_dup0.py @@ -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() diff --git a/test/TEX/multi-run.py b/test/TEX/multi-run.py index d4e2d798..686263d8 100644 --- a/test/TEX/multi-run.py +++ b/test/TEX/multi-run.py @@ -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'], """\ diff --git a/test/YACC/live.py b/test/YACC/live.py index 4934570d..0e5f1562 100644 --- a/test/YACC/live.py +++ b/test/YACC/live.py @@ -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 index e25fbd50..00000000 --- a/test/build-errors.py +++ /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() diff --git a/test/builderrors.py b/test/builderrors.py index 8c7c4ab7..28c9a0ae 100644 --- a/test/builderrors.py +++ b/test/builderrors.py @@ -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. diff --git a/test/import.py b/test/import.py index 9c5d3afb..85948f9a 100644 --- a/test/import.py +++ b/test/import.py @@ -96,6 +96,7 @@ tools = [ 'g77', 'gas', 'gcc', + 'gfortran', 'gnulink', 'gs', 'hpc++', diff --git a/test/option-k.py b/test/option-k.py index 0a46606a..4a460a2b 100644 --- a/test/option-k.py +++ b/test/option-k.py @@ -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() diff --git a/test/option/debug-includes.py b/test/option/debug-includes.py index 172cbb08..70857e66 100644 --- a/test/option/debug-includes.py +++ b/test/option/debug-includes.py @@ -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 diff --git a/test/option/debug-memoizer.py b/test/option/debug-memoizer.py index 8c8b2cf2..ad35b5d5 100644 --- a/test/option/debug-memoizer.py +++ b/test/option/debug-memoizer.py @@ -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 index 00000000..495f86dd --- /dev/null +++ b/test/option/stack-size.py @@ -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() diff --git a/test/scons-time/func/file.py b/test/scons-time/func/file.py index c9486c4b..079c125e 100644 --- a/test/scons-time/func/file.py +++ b/test/scons-time/func/file.py @@ -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) diff --git a/test/scons-time/mem/file.py b/test/scons-time/mem/file.py index a236df7d..a1e71812 100644 --- a/test/scons-time/mem/file.py +++ b/test/scons-time/mem/file.py @@ -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) diff --git a/test/scons-time/obj/file.py b/test/scons-time/obj/file.py index c8813973..3cf8e742 100644 --- a/test/scons-time/obj/file.py +++ b/test/scons-time/obj/file.py @@ -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) diff --git a/test/scons-time/run/aegis.py b/test/scons-time/run/aegis.py index 8f378cbd..641f1291 100644 --- a/test/scons-time/run/aegis.py +++ b/test/scons-time/run/aegis.py @@ -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')) diff --git a/test/scons-time/run/config/archive_list.py b/test/scons-time/run/config/archive_list.py index 8d48d262..8ddde3d0 100644 --- a/test/scons-time/run/config/archive_list.py +++ b/test/scons-time/run/config/archive_list.py @@ -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') diff --git a/test/scons-time/run/option/quiet.py b/test/scons-time/run/option/quiet.py index f5a3d8c0..453829cb 100644 --- a/test/scons-time/run/option/quiet.py +++ b/test/scons-time/run/option/quiet.py @@ -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() diff --git a/test/scons-time/run/option/verbose.py b/test/scons-time/run/option/verbose.py index fb95dab5..935e2a96 100644 --- a/test/scons-time/run/option/verbose.py +++ b/test/scons-time/run/option/verbose.py @@ -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() diff --git a/test/scons-time/run/subversion.py b/test/scons-time/run/subversion.py index 3839999e..757f6dff 100644 --- a/test/scons-time/run/subversion.py +++ b/test/scons-time/run/subversion.py @@ -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 index 00000000..7542bfcc --- /dev/null +++ b/test/scons-time/time/empty.py @@ -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() diff --git a/test/scons-time/time/file.py b/test/scons-time/time/file.py index f4046c9f..96bd035b 100644 --- a/test/scons-time/time/file.py +++ b/test/scons-time/time/file.py @@ -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 index 00000000..ca345b2f --- /dev/null +++ b/test/scons-time/time/no-result.py @@ -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() diff --git a/test/subclassing.py b/test/subclassing.py index 18be1bcc..31632a97 100644 --- a/test/subclassing.py +++ b/test/subclassing.py @@ -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 -#include -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() diff --git a/test/timestamp-fallback.py b/test/timestamp-fallback.py index 9d89d704..07749951 100644 --- a/test/timestamp-fallback.py +++ b/test/timestamp-fallback.py @@ -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 index 00000000..728db9cb --- /dev/null +++ b/timings/CPPPATH/SConstruct @@ -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 index 00000000..6507ea24 --- /dev/null +++ b/timings/CPPPATH/st.conf @@ -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 index 00000000..e1e38d20 --- /dev/null +++ b/timings/JTimer/SConstruct @@ -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 index 00000000..cf1ecbfe --- /dev/null +++ b/timings/JTimer/st.conf @@ -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 index 00000000..c56fb363 --- /dev/null +++ b/timings/SCons_Bars.py @@ -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 index 00000000..2332d732 --- /dev/null +++ b/timings/hundred/SConstruct @@ -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 index 00000000..adfb09eb --- /dev/null +++ b/timings/hundred/st.conf @@ -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)