Browse Source

Initial revision

David Rose 25 years ago
parent
commit
3ca8c86cf0

+ 26 - 0
ppremake/Depends.pp

@@ -0,0 +1,26 @@
+//
+// Depends.pp
+//
+// This file is loaded and run after each Sources.pp is read.  It
+// defines the inter-directory dependencies, which is useful for
+// determining build order.
+//
+
+#if $[eq $[DIR_TYPE], src]
+
+#if $[eq $[DEPENDS],]
+  #map local_libs TARGET(*/lib_target */noinst_lib_target)
+
+  // Allow the user to define additional DEPENDS targets in each
+  // Sources.pp.
+  #define DEPENDS
+  #set DEPENDS $[EXTRA_DEPENDS]
+
+  #forscopes lib_target bin_target noinst_bin_target
+    #set DEPENDS $[DEPENDS] $[local_libs $[DIRNAME],$[LOCAL_LIBS]] $[LOCAL_INCS]
+  #end lib_target bin_target noinst_bin_target
+
+  #set DEPENDS $[sort $[DEPENDS]]
+#endif
+
+#endif // DIR_TYPE

+ 334 - 0
ppremake/Global.stopgap.pp

@@ -0,0 +1,334 @@
+//
+// Global.stopgap.pp
+//
+// This file is read in before any of the individual Sources.pp files
+// are read.  It defines a few global variables to assist
+// Template.stopgap.pp.
+//
+
+// Define some various compile flags, derived from the variables set
+// in Config.pp.
+#if $[HAVE_PYTHON]
+  // We want to let the PYTHON_INCLUDE directory include wildcard characters.
+  #define python_ipath $[patsubst %,-I%,$[isdir $[PYTHON_IPATH]]]
+  #define python_lpath $[patsubst %,-L%,$[isdir $[PYTHON_LPATH]]]
+#endif
+
+#if $[HAVE_NSPR]
+  // We want to let the NSPR directories include wildcard characters.
+  #define nspr_ipath $[patsubst %,-I%,$[isdir $[NSPR_IPATH]]]
+  #define nspr_lpath $[patsubst %,-L%,$[isdir $[NSPR_LPATH]]]
+  #define nspr_libs $[NSPR_LIBS]
+#endif
+
+#if $[HAVE_ZLIB]
+  #define zlib_ipath $[ZLIB_IPATH:%=-I%]
+  #define zlib_lpath $[ZLIB_LPATH:%=-L%]
+  #define zlib_libs $[ZLIB_LIBS]
+#endif
+
+#if $[HAVE_SOXST]
+  #define soxst_ipath $[SOXST_IPATH:%=-I%]
+  #define soxst_lpath $[SOXST_LPATH:%=-L%]
+  #define soxst_libs $[SOXST_LIBS]
+#endif
+
+#if $[HAVE_GL]
+  #define gl_ipath $[GL_IPATH:%=-I%]
+  #define gl_lpath $[GL_LPATH:%=-L%]
+  #define gl_libs $[GL_LIBS]
+#endif
+
+#if $[HAVE_DX]
+  #define dx_ipath $[DX_IPATH:%=-I%]
+  #define dx_lpath $[DX_LPATH:%=-L%]
+  #define dx_libs $[DX_LIBS]
+#endif
+
+#if $[HAVE_MIKMOD]
+  #define mikmod_ipath $[MIKMOD_IPATH:%=-I%]
+  #define mikmod_cflags $[MIKMOD_CFLAGS]
+  #define mikmod_lpath $[MIKMOD_LPATH:%=-L%]
+  #define mikmod_libs $[MIKMOD_LIBS]
+#endif
+
+#if $[HAVE_GTKMM]
+  #define gtkmm_ipath $[GTKMM_IPATH:%=-I%]
+  #define gtkmm_cflags $[GTKMM_CFLAGS]
+  #define gtkmm_lpath $[GTKMM_LPATH:%=-L%]
+  #define gtkmm_libs $[GTKMM_LIBS]
+#endif
+
+#if $[and $[HAVE_MAYA],$[MAYA_LOCATION]]
+  #define maya_ipath -I$[MAYA_LOCATION]/include
+  #define maya_lpath -L$[MAYA_LOCATION]/lib
+  #define maya_ld $[MAYA_LOCATION]/bin/mayald
+#endif
+
+#if $[HAVE_NET]
+  #define net_ipath $[NET_IPATH:%=-I%]
+  #define net_lpath $[NET_LPATH:%=-L%]
+  #define net_libs $[NET_LIBS]
+#endif
+
+#if $[HAVE_AUDIO]
+  #define audio_ipath $[AUDIO_IPATH:%=-I%]
+  #define audio_lpath $[AUDIO_LPATH:%=-L%]
+  #define audio_libs $[AUDIO_LIBS]
+#endif
+
+
+// This variable, when evaluated in the scope of a particular directory,
+// will indicate true (i.e. nonempty) when the directory is truly built,
+// or false (empty) when the directory is not to be built.
+#defer build_directory \
+ $[and \
+     $[or $[not $[DIRECTORY_IF_GL]],$[HAVE_GL]], \
+     $[or $[not $[DIRECTORY_IF_DX]],$[HAVE_DX]], \
+     $[or $[not $[DIRECTORY_IF_GLX]],$[HAVE_GLX]], \
+     $[or $[not $[DIRECTORY_IF_GLUT]],$[HAVE_GLUT]], \
+     $[or $[not $[DIRECTORY_IF_WGL]],$[HAVE_WGL]], \
+     $[or $[not $[DIRECTORY_IF_RIB]],$[HAVE_RIB]], \
+     $[or $[not $[DIRECTORY_IF_PS2]],$[HAVE_PS2]], \
+     $[or $[not $[DIRECTORY_IF_SGIGL]],$[HAVE_SGIGL]], \
+     $[or $[not $[DIRECTORY_IF_VRPN]],$[HAVE_VRPN]], \
+     $[or $[not $[DIRECTORY_IF_NET]],$[HAVE_NET]], \
+     $[or $[not $[DIRECTORY_IF_AUDIO]],$[HAVE_AUDIO]], \
+     $[or $[not $[DIRECTORY_IF_GTKMM]],$[HAVE_GTKMM]], \
+     $[or $[not $[DIRECTORY_IF_MAYA]],$[HAVE_MAYA]], \
+      1 ]
+
+// This variable is true if we are building on some flavor of Unix.
+#define unix_platform $[ne $[PLATFORM],Win32]
+
+// This variable is true if we are building on some flavor of Windows.
+#define windows_platform $[eq $[PLATFORM],Win32]
+
+
+// This subroutine will set up the sources variable to reflect the
+// complete set of sources for this target, and also set the
+// alt_cflags, alt_libs, etc. as appropriate according to how the
+// various USE_* flags are set for the current target.
+#defsub get_sources
+  #define sources $[SOURCES]
+  #if $[ne $[HAVE_ZLIB],]
+    #set sources $[sources] $[IF_ZLIB_SOURCES]
+  #endif
+  #if $[ne $[HAVE_PYTHON],]
+    #set sources $[sources] $[IF_PYTHON_SOURCES]
+  #endif
+  
+  #define alt_cflags $[nspr_cflags] $[mikmod_cflags] $[python_cflags]
+  #define alt_ipath $[nspr_ipath] $[mikmod_ipath] $[python_ipath]
+  #define alt_lpath $[nspr_lpath] $[mikmod_lpath] $[python_lpath]
+  #define alt_libs $[nspr_libs] $[mikmod_libs]
+  #define alt_ld
+
+  // If any of a metalib's constituent libraries require interrogate,
+  // then so does the metalib itself.  To look this up, we need this map
+  // variable.
+  #map components TARGET(*/lib_target */noinst_lib_target)
+  
+  #if $[ne $[USE_ZLIB] $[components $[USE_ZLIB],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[zlib_cflags]
+    #set alt_ipath $[alt_ipath] $[zlib_ipath]
+    #set alt_lpath $[alt_lpath] $[zlib_lpath]
+    #set alt_libs $[alt_libs] $[zlib_libs]
+  #endif
+  #if $[ne $[USE_GL] $[components $[USE_GL],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[gl_cflags]
+    #set alt_ipath $[alt_ipath] $[gl_ipath]
+    #set alt_lpath $[alt_lpath] $[gl_lpath]
+    #set alt_libs $[alt_libs] $[gl_libs]
+  #endif
+  #if $[ne $[USE_DX] $[components $[USE_DX],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[dx_cflags]
+    #set alt_ipath $[alt_ipath] $[dx_ipath]
+    #set alt_lpath $[alt_lpath] $[dx_lpath]
+    #set alt_libs $[alt_libs] $[dx_libs]
+  #endif
+  #if $[ne $[USE_SOXST] $[components $[USE_SOXST],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[soxst_cflags]
+    #set alt_ipath $[alt_ipath] $[soxst_ipath]
+    #set alt_lpath $[alt_lpath] $[soxst_lpath]
+    #set alt_libs $[alt_libs] $[soxst_libs]
+  #endif
+  #if $[ne $[USE_NET] $[components $[USE_NET],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[net_cflags]
+    #set alt_ipath $[alt_ipath] $[net_ipath]
+    #set alt_lpath $[alt_lpath] $[net_lpath]
+    #set alt_libs $[alt_libs] $[net_libs]
+  #endif
+  #if $[ne $[USE_AUDIO] $[components $[USE_AUDIO],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[audio_cflags]
+    #set alt_ipath $[alt_ipath] $[audio_ipath]
+    #set alt_lpath $[alt_lpath] $[audio_lpath]
+    #set alt_libs $[alt_libs] $[audio_libs]
+  #endif
+  #if $[ne $[USE_GTKMM] $[components $[USE_GTKMM],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[gtkmm_cflags]
+    #set alt_ipath $[alt_ipath] $[gtkmm_ipath]
+    #set alt_lpath $[alt_lpath] $[gtkmm_lpath]
+    #set alt_libs $[alt_libs] $[gtkmm_libs]
+  #endif 
+  #if $[ne $[USE_MAYA] $[components $[USE_MAYA],$[COMPONENT_LIBS]],]
+    #set alt_cflags $[alt_cflags] $[maya_cflags]
+    #set alt_ipath $[alt_ipath] $[maya_ipath]
+    #set alt_lpath $[alt_lpath] $[maya_lpath]
+    #set alt_libs $[alt_libs] $[maya_libs]
+    #set alt_ld $[maya_ld]
+  #endif
+  #if $[unix_platform]
+    #set alt_libs $[alt_libs] $[UNIX_SYS_LIBS] $[components $[UNIX_SYS_LIBS],$[COMPONENT_LIBS]]
+  #endif
+#end get_sources
+
+// This subroutine will set when_defer, when_no_defer, and when_either
+// correctly to the set of libs we should link with for current
+// target.
+#defsub get_libs
+  // For the WHEN_DEFER case, we need to know the complete set of
+  // metalibs that encapsulates each of our LOCAL_LIBS.  In the case
+  // where a particular library is not part of a metalib, we include the
+  // library itself.
+  
+  // These map variables are handy to determine that.
+  #map module COMPONENT_LIBS(*/metalib_target)
+  #map all_libs TARGET(*/static_lib_target */lib_target */noinst_lib_target */metalib_target)
+  #define when_defer
+  #foreach lib $[LOCAL_LIBS]
+    // Only consider libraries that we're actually building.
+    #if $[all_libs $[build_directory],$[lib]]
+      #define modmeta $[module $[TARGET],$[lib]]
+      #if $[ne $[modmeta],]
+        #set when_defer $[when_defer] $[modmeta]
+      #else
+        #set when_defer $[when_defer] $[lib]
+      #endif
+    #endif
+  #end lib
+  #set when_defer $[unique $[when_defer]] $[patsubst %:m,%,$[filter %:m,$[OTHER_LIBS]]]
+  
+  // Also filter out the libraries we don't want from when_no_defer, although
+  // we don't need to translate these to metalibs.
+  #define when_no_defer
+  #foreach lib $[COMPONENT_LIBS] $[LOCAL_LIBS]
+    #if $[all_libs $[build_directory],$[lib]]
+      #set when_no_defer $[when_no_defer] $[lib]
+    #endif
+  #end lib
+  #set when_no_defer $[unique $[when_no_defer]] $[patsubst %:c,%,$[filter %:c,$[OTHER_LIBS]]]
+  
+  // Finally, get the set of libraries that we want in either case.  At
+  // the moment, this is just the set of libraries in OTHER_LIBS that's
+  // not flagged with either a :c or a :m.
+  #define when_either $[filter-out %:m %:c,$[OTHER_LIBS]]
+#end get_libs
+
+
+// This subroutine converts depend_libs from a list of plain library names
+// to a list of the form libname.so or libname.a, according to whether the
+// named libraries are static or dynamic.
+#defsub convert_depend_libs
+  #map static_libs TARGET(*/static_lib_target)
+  #map dynamic_libs TARGET(*/lib_target */metalib_target)
+  #map all_libs TARGET(*/static_lib_target */lib_target */metalib_target)
+  #define new_depend_libs
+  #foreach lib $[depend_libs]
+    // Make sure the library is something we're actually building.
+    #if $[all_libs $[build_directory],$[lib]]
+      #define libname $[static_libs lib$[TARGET].a,$[lib]] $[dynamic_libs lib$[TARGET].so,$[lib]]
+      #if $[eq $[libname],]
+  Warning: No such library $[lib], dependency of $[DIRNAME].
+      #else
+        #set new_depend_libs $[new_depend_libs] $[libname]
+      #endif
+    #endif
+  #end lib
+  #set depend_libs $[sort $[new_depend_libs]]
+#end convert_depend_libs
+
+
+// This subroutine determines the set of libraries our various targets
+// depend on.  This is a complicated definition.  It is the union of
+// all of our targets' dependencies, except:
+
+// If a target is part of a metalib, it depends (a) directly on all of
+// its normal library dependencies that are part of the same metalib,
+// and (b) indirectly on all of the metalibs that every other library
+// dependency is part of.  If a target is not part of a metalib, it is
+// the same as case (b) above.
+#defsub get_depend_libs
+  #map module COMPONENT_LIBS(*/metalib_target)
+
+  #define depend_libs
+  #forscopes lib_target noinst_lib_target
+    #define metalib $[module $[TARGET],$[TARGET]]
+    #if $[ne $[metalib],]
+      // This library is included on a metalib.
+      #foreach depend $[LOCAL_LIBS]
+        #define depend_metalib $[module $[TARGET],$[depend]]
+        #if $[eq $[depend_metalib],$[metalib]]
+          // Here's a dependent library in the *same* metalib.
+          #set depend_libs $[depend_libs] $[depend]
+        #elif $[ne $[depend_metalib],]
+          // This dependent library is in a *different* metalib.
+          #set depend_libs $[depend_libs] $[depend_metalib]
+        #else
+          // This dependent library is not in any metalib.
+          #set depend_libs $[depend_libs] $[depend]
+        #endif
+      #end depend
+    #else
+      // This library is *not* included on a metalib.
+      #foreach depend $[LOCAL_LIBS]
+        #define depend_metalib $[module $[TARGET],$[depend]]
+        #if $[ne $[depend_metalib],]
+          // This dependent library is on a metalib.
+          #set depend_libs $[depend_libs] $[depend_metalib]
+        #else
+          // This dependent library is not in any metalib.
+          #set depend_libs $[depend_libs] $[depend]
+        #endif
+      #end depend
+    #endif
+  #end lib_target noinst_lib_target
+  
+  // These will never be part of a metalib.
+  #forscopes static_lib_target bin_target noinst_bin_target metalib_target
+    #foreach depend $[LOCAL_LIBS]
+      #define depend_metalib $[module $[TARGET],$[depend]]
+      #if $[ne $[depend_metalib],]
+        // This dependent library is on a metalib.
+        #set depend_libs $[depend_libs] $[depend_metalib]
+      #else
+        // This dependent library is not in any metalib.
+        #set depend_libs $[depend_libs] $[depend]
+      #endif
+    #end depend
+  #end static_lib_target bin_target noinst_bin_target metalib_target
+
+  // In case we're defining any metalibs, these depend directly on
+  // their components as well.
+  #set depend_libs $[depend_libs] $[COMPONENT_LIBS(metalib_target)]
+
+  // Now correct all the libraries listed in depend_libs to refer to a
+  // real library name.
+  #map static_libs TARGET(*/static_lib_target)
+  #map dynamic_libs TARGET(*/lib_target */metalib_target)
+  #map all_libs TARGET(*/static_lib_target */lib_target */metalib_target)
+  #define new_depend_libs
+  #foreach lib $[sort $[depend_libs]]
+    // Make sure the library is something we're actually building.
+    #if $[all_libs $[build_directory],$[lib]]
+      #define libname $[static_libs lib$[TARGET].a,$[lib]] $[dynamic_libs lib$[TARGET].so,$[lib]]
+      #if $[eq $[libname],]
+  Warning: No such library $[lib], dependency of $[DIRNAME].
+      #else
+        #set new_depend_libs $[new_depend_libs] $[libname]
+      #endif
+    #endif
+  #end lib
+  #set depend_libs $[sort $[new_depend_libs]]
+
+#end get_depend_libs

+ 20 - 0
ppremake/Makefile.am

@@ -0,0 +1,20 @@
+bin_PROGRAMS = ppremake
+
+ppremake_SOURCES =						\
+    find_searchpath.cxx find_searchpath.h			\
+    gnu_getopt.c gnu_getopt.h					\
+    ppCommandFile.cxx ppCommandFile.h ppDirectoryTree.cxx	\
+    ppDirectoryTree.h ppMain.cxx ppMain.h			\
+    ppFilenamePattern.cxx					\
+    ppFilenamePattern.h ppNamedScopes.cxx ppNamedScopes.h	\
+    ppScope.cxx ppScope.h ppSubroutine.cxx ppSubroutine.h	\
+    ppremake.cxx ppremake.h tokenize.cxx			\
+    tokenize.h
+
+data_DATA = \
+    System.pp Depends.pp Template.autoconf.pp \
+    Global.stopgap.pp Template.stopgap.pp
+
+EXTRA_DIST =\
+    System.pp Depends.pp Template.autoconf.pp \
+    Global.stopgap.pp Template.stopgap.pp

+ 34 - 0
ppremake/System.pp

@@ -0,0 +1,34 @@
+//
+// System.pp
+//
+// This is a system-wide configure file for ppremake.  It's normally
+// #included from a package-specific Config.pp in the root of the
+// source tree.  It makes variable declarations that are not normally
+// user-editable, but are required to set up the normal processing of
+// ppremake.
+//
+
+
+// Define DIR_TYPE as "src", since that's the most common kind of source
+// file.
+#if $[eq $[DIR_TYPE],]
+  #define DIR_TYPE src
+#endif
+
+// Define where to look for the various kinds of system files.
+#if $[eq $[DEPENDS_FILE],]
+  #define DEPENDS_FILE $[PPREMAKE_DIR]/Depends.pp
+#endif
+
+#if $[eq $[GLOBAL_FILE],]
+  #define GLOBAL_FILE $[PPREMAKE_DIR]/Global.$[BUILD_TYPE].pp
+#endif
+
+#if $[eq $[TEMPLATE_FILE],]
+  #define TEMPLATE_FILE $[PPREMAKE_DIR]/Template.$[BUILD_TYPE].pp
+#endif
+
+// Include the global definitions for the template type, if the file
+// is there.
+#sinclude $[GLOBAL_FILE]
+

+ 922 - 0
ppremake/Template.autoconf.pp

@@ -0,0 +1,922 @@
+//
+// Template.autoconf.pp
+//
+// This file defines the set of output files that will be generated to
+// support an autoconf/automake style build.  This works particularly
+// well when gcc/g++ will be used to compile, for instance on Linux.
+//
+
+#defer get_sys_libs $[subst -ldl,@libdl@,$[patsubst %,-l%,$[UNIX_SYS_LIBS]]]
+
+// First, check to see if the entire directory has been switched out.
+#define omit
+#if $[DIRECTORY_IF_GL]
+  #set omit $[not $[HAVE_GL]]
+#endif
+#if $[DIRECTORY_IF_GLX]
+  #set omit $[not $[HAVE_GLX]]
+#endif
+#if $[DIRECTORY_IF_WGL]
+  #set omit $[not $[HAVE_WGL]]
+#endif
+#if $[DIRECTORY_IF_GLUT]
+  #set omit $[not $[HAVE_GLUT]]
+#endif
+#if $[DIRECTORY_IF_SGIGL]
+  #set omit $[not $[HAVE_SGIGL]]
+#endif
+#if $[DIRECTORY_IF_DX]
+  #set omit $[not $[HAVE_DX]]
+#endif
+#if $[DIRECTORY_IF_PS2]
+  #set omit $[not $[HAVE_PS2]]
+#endif
+#if $[DIRECTORY_IF_RIB]
+  #set omit $[not $[HAVE_RIB]]
+#endif
+#if $[DIRECTORY_IF_VRPN]
+  #set omit $[not $[HAVE_VRPN]]
+#endif
+
+//////////////////////////////////////////////////////////////////////
+#if $[eq $[DIR_TYPE], src]
+//////////////////////////////////////////////////////////////////////
+
+// For a source directory, build a Makefile.am with a number of targets.
+
+#output Makefile.am
+#format makefile
+# Makefile.am generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+
+#if $[omit]
+  // If we're omitting the directory, everything becomes an extra_dist.  
+EXTRA_DIST = Sources.pp $[EXTRA_DIST] $[SOURCES(static_lib_target noinst_lib_target lib_target noinst_bin_target bin_target test_bin_target)]
+
+#else   // $[omit]
+
+// We define a map variable that allows us to look up all the libs in
+// the various directories by target name.  With this map variable, we
+// can translate the list of local_libs (which is simply a list of
+// library names, with no directory information), into a list of
+// relative filenames to each library.
+#map local_libs TARGET(*/static_lib_target */lib_target */noinst_lib_target)
+
+#define all_include_dirs
+
+lib_LTLIBRARIES = $[TARGET(static_lib_target lib_target):%=lib%.la]
+noinst_LTLIBRARIES = $[TARGET(noinst_lib_target):%=lib%.la]
+bin_PROGRAMS = $[TARGET(bin_target)]
+noinst_PROGRAMS = $[TARGET(noinst_bin_target)]
+EXTRA_PROGRAMS = $[TARGET(test_bin_target)]
+
+#if $[ne $[YACC_PREFIX],]
+YFLAGS = -d --name-prefix=$[YACC_PREFIX] $[YFLAGS]
+LFLAGS = -P$[YACC_PREFIX] -olex.yy.c $[LFLAGS]
+#else
+YFLAGS = -d $[YFLAGS]
+LFLAGS = $[LFLAGS]
+#endif
+
+#define alt_cflags @nspr_cflags@ @python_cflags@
+#define alt_lflags @nspr_lflags@
+#define alt_libs @nspr_libs@
+#if $[ne $[USE_ZLIB],]
+  #define alt_cflags $[alt_cflags] @zlib_cflags@
+  #define alt_lflags $[alt_lflags] @zlib_lflags@
+  #define alt_libs $[alt_libs] @zlib_libs@
+#endif
+#if $[ne $[USE_GL],]
+  #define alt_cflags $[alt_cflags] @gl_cflags@ @glut_cflags@
+  #define alt_lflags $[alt_lflags] @gl_lflags@ @glut_lflags@
+  #define alt_libs $[alt_libs] @gl_libs@ @glut_libs@
+#endif
+
+#define built_sources
+#define install_data
+
+#forscopes static_lib_target lib_target noinst_lib_target
+
+// This map variable lets us identify which metalib, if any, is
+// including this particular library.
+#map module LOCAL_LIBS(*/metalib_target)
+
+// And this defines the complete set of libs we depend on: the
+// LOCAL_LIBS we listed as directly depending on, plus all of the
+// LOCAL_LIBS *those* libraries listed, and so on.
+#define complete_local_libs $[closure local_libs,$[LOCAL_LIBS]]
+
+
+#if $[ne $[IF_ZLIB_SOURCES],]
+if HAVE_ZLIB
+EXTRA_ZLIB = $[IF_ZLIB_SOURCES]
+else
+EXTRA_ZLIB =
+endif
+#define SOURCES $[SOURCES] $(EXTRA_ZLIB)
+#endif
+
+#define local_incs $[local_libs $[RELDIR],$[complete_local_libs]] $[RELDIR($[LOCAL_INCS:%=%/])]
+
+// Check for interrogate.
+#if $[eq $[IGATESCAN], all]
+  #define IGATESCAN $[filter-out %.I %.lxx %.yxx %.N,$[SOURCES]]
+#endif
+#if $[ne $[IGATESCAN],]
+  #define IGATEFILE $[TARGET].in.cxx
+  #define IGATEDBFILE lib$[TARGET].in
+
+  #define IGATELIBRARY lib$[TARGET]
+  #define IGATEMODULE lib$[module $[TARGET],$[TARGET]]
+  #if $[eq $[IGATEMODULE], lib]
+    #define IGATEMODULE $[IGATELIBRARY]
+  #endif
+
+IGATESCAN = $[IGATESCAN]
+$[IGATEFILE] : $(IGATESCAN)
+	@dtool@/bin/interrogate $[IGATEFLAGS] @system_igate@ -DCPPPARSER -D__cplusplus -I@dtool@/include/parser-inc @trees_inc@ $[local_incs:%=-I%] $[alt_cflags] $[CDEFINES] -module "$[IGATEMODULE]" -library "$[IGATELIBRARY]" -oc $[IGATEFILE] -od $[IGATEDBFILE] -fnames -string -refcount -assert -promiscuous -python $(IGATESCAN)
+#set built_sources $[built_sources] $[IGATEFILE]
+#set install_data $[install_data] $[IGATEDBFILE]
+#endif
+
+  #define SOURCES $[SOURCES] $[IGATEFILE]
+
+lib$[TARGET]_la_SOURCES = $[SOURCES]
+lib$[TARGET]_la_LIBADD = $[OTHER_LIBS:%=-l%] $[alt_libs] $[get_sys_libs]
+
+#set all_include_dirs $[all_include_dirs] $[local_incs]
+  
+
+
+#end static_lib_target lib_target noinst_lib_target
+
+#forscopes bin_target noinst_bin_target test_bin_target
+#if $[ne $[IF_ZLIB_SOURCES],]
+if HAVE_ZLIB
+EXTRA_ZLIB = $[IF_ZLIB_SOURCES]
+else
+EXTRA_ZLIB =
+endif
+#define $[SOURCES] $(EXTRA_ZLIB)
+#endif
+
+// This defines the complete set of libs we depend on: the LOCAL_LIBS
+// we listed as directly depending on, plus all of the LOCAL_LIBS
+// *those* libraries listed, and so on.
+#define complete_local_libs $[closure local_libs,$[LOCAL_LIBS]]
+
+$[TARGET]_SOURCES = $[SOURCES]
+$[TARGET]_LDADD = $[local_libs $[RELDIR]/lib$[TARGET].la,$[complete_local_libs]] $[OTHER_LIBS:%=-l%] $[alt_libs] $[get_sys_libs]
+#set all_include_dirs $[all_include_dirs] $[local_libs $[RELDIR],$[complete_local_libs]]
+#set all_include_dirs $[all_include_dirs] $[RELDIR($[LOCAL_INCS:%=%/])]
+
+#end bin_target noinst_bin_target test_bin_target
+
+include_HEADERS = $[sort $[INSTALL_HEADERS(static_lib_target lib_target bin_target)] $[INSTALL_HEADERS]]
+#set install_data $[install_data] $[INSTALL_DATA]
+
+#if $[ne $[INSTALL_PARSER_INC],]
+parserincdir = @includedir@/parser-inc
+parserinc_HEADERS = $[INSTALL_PARSER_INC]
+#endif
+
+INCLUDES = $[patsubst %,-I%,$[sort $[all_include_dirs]]] @trees_inc@ $[alt_cflags]
+LDFLAGS = @ldflags@ @trees_lflags@ $[alt_lflags]
+EXTRA_DIST = Sources.pp $[EXTRA_DIST] $[INSTALL_LIBS] $[INSTALL_SCRIPTS]$[install_data]
+BUILT_SOURCES =$[built_sources]
+data_DATA =$[install_data]
+
+#endif   // $[omit]
+
+#end Makefile.am
+
+
+//////////////////////////////////////////////////////////////////////
+#elif $[eq $[DIR_TYPE], metalib]
+//////////////////////////////////////////////////////////////////////
+
+// A metalib directory is similar to a regular source directory, 
+// but a little simpler.
+
+#output Makefile.am
+#format makefile
+# Makefile.am generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+
+#if $[omit]
+  // If we're omitting the directory, everything becomes an extra_dist.  
+EXTRA_DIST = Sources.pp $[EXTRA_DIST] $[SOURCES(static_lib_target noinst_lib_target lib_target noinst_bin_target bin_target test_bin_target)]
+
+#else   // $[omit]
+
+// We define a map variable that allows us to look up all the libs in
+// the various directories by target name.  With this map variable, we
+// can translate the list of local_libs (which is simply a list of
+// library names, with no directory information), into a list of
+// relative filenames to each library.
+#map local_libs TARGET(*/static_lib_target */lib_target */noinst_lib_target)
+
+#define all_include_dirs $(includedir)
+
+lib_LTLIBRARIES = $[TARGET(metalib_target):%=lib%.la]
+
+#define alt_cflags @nspr_cflags@ @python_cflags@
+#define alt_lflags @nspr_lflags@
+#define alt_libs @nspr_libs@
+#if $[ne $[USE_ZLIB],]
+  #define alt_cflags $[alt_cflags] @zlib_cflags@
+  #define alt_lflags $[alt_lflags] @zlib_lflags@
+  #define alt_libs $[alt_libs] @zlib_libs@
+#endif
+#if $[ne $[USE_GL],]
+  #define alt_cflags $[alt_cflags] @gl_cflags@
+  #define alt_lflags $[alt_lflags] @gl_lflags@
+  #define alt_libs $[alt_libs] @gl_libs@
+#endif
+
+#forscopes metalib_target
+#if $[ne $[IF_PYTHON_SOURCES],]
+if HAVE_PYTHON
+EXTRA_PYTHON = $[IF_PYTHON_SOURCES]
+else
+EXTRA_PYTHON =
+endif
+#define SOURCES $[SOURCES] $(EXTRA_PYTHON)
+#endif
+
+// This defines the complete set of libs we depend on: the LOCAL_LIBS
+// we listed as directly depending on, plus all of the LOCAL_LIBS
+// *those* libraries listed, and so on.
+#define complete_local_libs $[closure local_libs,$[LOCAL_LIBS]]
+
+lib$[TARGET]_la_SOURCES = $[SOURCES]
+lib$[TARGET]_la_LIBADD = $[complete_local_libs:%=-l%] $[get_sys_libs]
+
+#end metalib_target
+
+INCLUDES = $[patsubst %,-I%,$[sort $[all_include_dirs]]] @trees_inc@ $[alt_cflags]
+LDFLAGS = @ldflags@ -L$(libdir) -rpath $(libdir) @trees_lflags@ $[alt_lflags]
+EXTRA_DIST = Sources.pp $[EXTRA_DIST]
+
+#endif   // $[omit]
+
+#end Makefile.am
+
+
+//////////////////////////////////////////////////////////////////////
+#elif $[eq $[DIR_TYPE], group]
+//////////////////////////////////////////////////////////////////////
+
+#output Makefile.am
+#format makefile
+# Makefile.am generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+
+SUBDIRS = $[SUBDIRS]
+
+EXTRA_DIST = Sources.pp $[EXTRA_DIST]
+
+#end Makefile.am
+
+
+
+//////////////////////////////////////////////////////////////////////
+#elif $[eq $[DIR_TYPE], toplevel]
+//////////////////////////////////////////////////////////////////////
+
+#output Makefile.am
+#format makefile
+# Makefile.am generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+
+FIRSTBUILD_SUBDIRS = $[filter_out metalibs,$[SUBDIRS]]
+SUBDIRS = $[SUBDIRS]
+
+#define INSTALL_HEADERS $[INSTALL_HEADERS] $[CONFIG_HEADER]
+
+include_HEADERS = $[INSTALL_HEADERS]
+
+EXTRA_DIST = Sources.pp Config.pp $[EXTRA_DIST]
+
+
+# We define this custom rule for all-recursive as an ordering hack.
+# It's just like the default rule, except that it traverses through
+# only FIRSTBUILD_SUBDIRS, instead of all of SUBDIRS.  The idea is
+# that first we build everything in FIRSTBUILD_SUBDIRS, and then when
+# we're installing, we build everything in SUBDIRS as well.  This hack
+# is necessary to build targets in metalibs that link directly with
+# installed shared libraries.
+
+all-recursive:
+	@set fnord $(MAKEFLAGS); amf=$$2; \
+	dot_seen=no; \
+	target=`echo $@ | sed s/-recursive//`; \
+	list='$(FIRSTBUILD_SUBDIRS)'; for subdir in $$list; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    dot_seen=yes; \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	   || case "$$amf" in *=*) exit 1;; *k*) fail=yes;; *) exit 1;; esac; \
+	done; \
+	if test "$$dot_seen" = "no"; then \
+	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+	fi; test -z "$$fail"
+
+#end Makefile.am
+
+#output configure.in
+#format straight
+dnl configure.in generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT($[SAMPLE_SOURCE_FILE])
+AM_INIT_AUTOMAKE($[PACKAGE], $[VERSION])
+
+#if $[eq $[CONFIG_HEADER],]
+dnl This package doesn't care about a generated config.h file.  This causes
+dnl a few problems with automake, so we fake it out with this hack.
+AM_CONFIG_HEADER(ignore_config.h)
+#else
+AM_CONFIG_HEADER($[CONFIG_HEADER])
+#endif
+
+if test "${CTPROJS+set}" = set; then
+  if test "${$[upcase $[PACKAGE]]+set}" != set; then
+    echo ""
+    echo "The environment variable CTPROJS is currently set, indicating"
+    echo "you are attached to one or more trees, but you are not attached"
+    echo "to $[upcase $[PACKAGE]].  Either unattach from everything, or attach to $[upcase $[PACKAGE]]."
+    echo ""
+    exit 1
+  fi
+
+  # If we're currently attached, the install directory is the same as
+  # the source directory.
+  if test "$prefix" != "NONE"; then
+    echo ""
+    echo The environment variable CTPROJS is currently set, indicating
+    echo you are attached to one or more trees.  When you configure the
+    echo package while attached, you cannot specify a --prefix to install
+    echo the built sources--it always installs in the source directory.
+    echo ""
+    exit 1
+  fi
+
+  prefix=$$[upcase $[PACKAGE]]
+fi
+
+AC_PREFIX_DEFAULT($[INSTALL_DIR])
+AC_PROG_MAKE_SET
+AC_CANONICAL_HOST
+
+# If we have a CFLAGS variable but not a CXXFLAGS variable, let them
+# be the same.
+if test "${CXXFLAGS+set}" != set -a "${CFLAGS+set}" = set; then
+  CXXFLAGS=$CFLAGS
+fi
+
+# Save these variables for later, so we can easily append to them or
+# change them.
+user_ldflags=${LDFLAGS-}
+user_cflags=${CFLAGS-}
+user_cxxflags=${CXXFLAGS-}
+
+dnl Choose a suitable set of system-dependent interrogate flags.
+case "$host_os" in
+  irix*) system_igate="-D__mips__ -D__MIPSEB__";;
+  linux-gnu*) system_igate="-D__i386__";;
+esac
+AC_SUBST(system_igate)
+
+dnl Checks for programs.
+AC_PROG_CC
+AC_PROG_CXX
+AM_PROG_LEX
+AM_DISABLE_STATIC
+AM_PROG_LIBTOOL
+AC_PATH_XTRA
+
+dnl We require specifically Bison, not any other flavor of Yacc.
+AC_CHECK_PROGS(YACC, 'bison -y')
+
+
+AC_ARG_WITH(optimize,
+[  --with-optimize=LEVEL   Specify the optimization/debug symbol level (1-4, default 1)])
+
+if test "${with_optimize-no}" = "no"; then
+  with_optimize=$[OPTIMIZE]
+fi
+
+if test "$with_optimize" = "1"; then
+  # Optimize level 1: No optimizations, and full debug symbols.  
+  if test "${ac_cv_prog_gcc}" = "yes"; then
+    CFLAGS="$user_cflags -g -Wall"
+    CXXFLAGS="$user_cxxflags -g -Wall"
+  else
+    CFLAGS="$user_cflags -g"
+    CXXFLAGS="$user_cxxflags -g"
+  fi
+
+elif test "$with_optimize" = "2"; then
+  # Optimize level 2: Compiler optimizations, and full debug if supported.
+  if test "${ac_cv_prog_gcc}" = "yes"; then
+    CFLAGS="$user_cflags -O2 -g -Wall"
+    CXXFLAGS="$user_cxxflags -O2 -g -Wall"
+  else
+    CFLAGS="$user_cflags -O"
+    CXXFLAGS="$user_cxxflags -O"
+  fi
+
+elif test "$with_optimize" = "3"; then
+  # Optimize level 3: Compiler optimizations, without debug symbols.
+  if test "${ac_cv_prog_gcc}" = "yes"; then
+    CFLAGS="$user_cflags -O2 -Wall"
+    CXXFLAGS="$user_cxxflags -O2 -Wall"
+  else
+    CFLAGS="$user_cflags -O"
+    CXXFLAGS="$user_cxxflags -O"
+  fi
+
+elif test "$with_optimize" = "4"; then
+  # Optimize level 4: As above, with asserts removed.
+  if test "${ac_cv_prog_gcc}" = "yes"; then
+    CFLAGS="$user_cflags -O2 -Wall -DNDEBUG"
+    CXXFLAGS="$user_cxxflags -O2 -Wall -DNDEBUG"
+  else
+    CFLAGS="$user_cflags -O -DNDEBUG"
+    CXXFLAGS="$user_cxxflags -O -DNDEBUG"
+  fi
+
+else
+  echo "Invalid optimize level: $with_optimize"
+  exit 0
+fi
+
+trees_inc=
+trees_lflags=
+
+#foreach require $[REQUIRED_TREES]
+
+AC_ARG_WITH($[require],
+[  --with-$[require]=DIR        Prefix where $[upcase $[require]] is installed (same as --prefix)])
+
+
+if test "${CTPROJS+set}" = set; then
+  if test "$with_$[require]" != ""; then
+    echo ""
+    echo "The environment variable CTPROJS is currently set, indicating"
+    echo "you are attached to one or more trees.  When you configure the"
+    echo "package while attached, you cannot specify a directory to search"
+    echo "for --$[require]; it will always search in the currently attached $[upcase $[require]]."
+    echo ""
+    exit 1
+  fi
+  if test "${$[upcase $[require]]+set}" != set; then
+    echo ""
+    echo "The environment variable CTPROJS is currently set, indicating"
+    echo "you are attached to one or more trees, but you are not attached"
+    echo "to $[upcase $[require]].  Either unattach from everything, or attach to $[upcase $[require]]."
+    echo ""
+    exit 1
+  fi
+
+  $[require]='${$[upcase $[require]]}'
+else
+  # No attachments--respect the --with-$[require] parameter.
+
+  if test "$with_$[require]" != "" -a "$with_$[require]" != "no" -a "$with_$[require]" != "yes"; then
+    $[require]=$with_$[require]
+  else
+    $[require]='${prefix}'
+  fi
+  trees_inc="$trees_inc -I"'$($[require])/include'
+  trees_lflags="$trees_lflags -L"'$($[require])/lib'
+fi
+
+AC_SUBST($[require])
+
+#end require
+AC_SUBST(trees_inc)
+AC_SUBST(trees_lflags)
+
+dnl First, we'll test for C-specific features.
+AC_LANG_C
+
+dnl Checks for libraries.
+libdl=
+libm=
+AC_CHECK_LIB(dl, dlopen, libdl=-ldl)
+AC_CHECK_LIB(m, sin, libm=-lm)
+AC_SUBST(libdl)
+AC_SUBST(libm)
+
+// Only bother to make the following tests if we're actually building
+// a config.h.
+#if $[ne $[CONFIG_HEADER],]
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(malloc.h alloca.h unistd.h io.h minmax.h sys/types.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_BIGENDIAN
+AC_GETTIMEOFDAY
+
+dnl Checks for library functions.
+AC_CHECK_FUNCS(getopt getopt_long_only)
+
+
+dnl Now we can test some C++-specific features.
+AC_LANG_CPLUSPLUS
+AC_HEADER_IOSTREAM
+AC_CHECK_HEADERS(sstream)
+AC_NAMESPACE
+AC_IOS_BINARY
+#endif
+
+AC_LANG_C
+
+AC_ARG_WITH(python,
+[  --with-python=DIR       Prefix where Python is installed (usually /usr/local)])
+
+have_python=no
+include_python=
+if test "$with_python" != "no"; then
+  if test "$with_python" = "yes" -o "$with_python" = ""; then
+    AC_SEARCH_HPACKAGE(/usr/local /usr,
+                       python1.6 python,
+	               Python.h, python)
+  else
+    AC_SEARCH_HPACKAGE($with_python,
+                       python1.6 python,
+	               Python.h, python)
+  fi
+
+  if test "$with_python" != ""; then
+    if test "$python_PKG" != "yes"; then
+      dnl  If the user specified to search for python but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate Python package.  Use --with-python=directory,"
+      echo "     e.g. --with-python=/usr/local, or just --with-python."
+      echo "     If Python is not installed, specify --without-python."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$python_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_PYTHON)
+#endif
+    have_python=yes
+    python_cflags=-I$python_INCLUDE
+  fi
+fi
+
+AC_SUBST(have_python)
+AC_SUBST(python_cflags)
+AM_CONDITIONAL(HAVE_PYTHON, test "$have_python" = "yes")
+
+
+AC_ARG_WITH(nspr,
+[  --with-nspr=DIR         Prefix where NSPR is installed (usually /usr/local/mozilla)])
+
+have_nspr=no
+include_nspr=
+if test "$with_nspr" != "no"; then
+  if test "$with_nspr" = "yes" -o "$with_nspr" = ""; then
+    AC_SEARCH_PACKAGE(/usr/local/mozilla /usr/local/mozilla/dist/*,,
+	              nspr.h, nspr3, PR_Init, nspr)
+  else
+    AC_SEARCH_PACKAGE($with_nspr $with_nspr/dist/*,,
+	              nspr.h, nspr3, PR_Init, nspr)
+  fi
+
+  if test "$with_nspr" != ""; then
+    if test "$nspr_PKG" != "yes"; then
+      dnl  If the user specified to search for NSPR but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate NSPR package.  Use --with-nspr=directory,"
+      echo "     e.g. --with-nspr=/usr/local, or just --with-nspr."
+      echo "     If NSPR is not installed, specify --without-nspr."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$nspr_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_NSPR)
+#endif
+    have_nspr=yes
+    nspr_cflags="-I$nspr_INCLUDE"
+    nspr_ldflags="-L$nspr_LIB"
+    nspr_libs="-lnspr3"
+  fi
+fi
+
+AC_SUBST(have_nspr)
+AC_SUBST(nspr_cflags)
+AC_SUBST(nspr_lflags)
+AC_SUBST(nspr_libs)
+AM_CONDITIONAL(HAVE_NSPR, test "$have_nspr" = "yes")
+
+
+AC_ARG_WITH(zlib,
+[  --with-zlib=DIR         Prefix where zlib is installed (usually /usr)])
+
+have_zlib=no
+include_zlib=
+if test "$with_zlib" != "no"; then
+  if test "$with_zlib" = "yes" -o "$with_zlib" = ""; then
+    AC_SEARCH_PACKAGE(/usr /usr/local,,
+	              zlib.h, z, gzopen, zlib)
+  else
+    AC_SEARCH_PACKAGE($with_zlib,,
+	              zlib.h, z, gzopen, zlib)
+  fi
+
+  if test "$with_zlib" != ""; then
+    if test "$zlib_PKG" != "yes"; then
+      dnl  If the user specified to search for zlib but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate zlib package.  Use --with-zlib=directory,"
+      echo "     e.g. --with-zlib=/usr/local, or just --with-zlib."
+      echo "     If zlib is not installed, specify --without-zlib."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$zlib_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_ZLIB)
+#endif
+    have_zlib=yes
+    if test "$zlib_INCLUDE" != ""; then
+      zlib_cflags="-I$zlib_INCLUDE"
+    fi
+    if test "$zlib_LIB" != ""; then
+      zlib_lflags="-L$zlib_LIB"
+    fi
+    zlib_libs="-lz"
+  fi
+fi
+
+AC_SUBST(have_zlib)
+AC_SUBST(zlib_cflags)
+AC_SUBST(zlib_lflags)
+AC_SUBST(zlib_libs)
+AM_CONDITIONAL(HAVE_ZLIB, test "$have_zlib" = "yes")
+
+
+AC_ARG_WITH(gl,
+[  --with-gl=DIR           Prefix where OpenGL is installed (usually /usr)])
+
+have_gl=no
+include_gl=
+if test "$with_gl" != "no"; then
+  if test "$with_gl" = "yes" -o "$with_gl" = ""; then
+    AC_SEARCH_PACKAGE(/usr /usr/local,,
+	              GL/gl.h, GL, glVertex3f, gl)
+  else
+    AC_SEARCH_PACKAGE($with_gl,,
+	              GL/gl.h, GL, glVertex3f, gl)
+  fi
+
+  if test "$with_gl" != ""; then
+    if test "$gl_PKG" != "yes"; then
+      dnl  If the user specified to search for OpenGL but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate OpenGL package.  Use --with-gl=directory,"
+      echo "     e.g. --with-gl=/usr/local, or just --with-gl."
+      echo "     If OpenGL is not installed, specify --without-gl."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$gl_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_GL)
+#endif
+    have_gl=yes
+    if test "$gl_INCLUDE" != ""; then
+      gl_cflags="-I$gl_INCLUDE"
+    fi
+    if test "$gl_LIB" != ""; then
+      gl_lflags="-L$gl_LIB"
+    fi
+    gl_libs="-lGL -lGLU"
+  fi
+fi
+
+AC_SUBST(have_gl)
+AC_SUBST(gl_cflags)
+AC_SUBST(gl_lflags)
+AC_SUBST(gl_libs)
+AM_CONDITIONAL(HAVE_GL, test "$have_gl" = "yes")
+
+
+AC_ARG_WITH(glu,
+[  --with-glu=DIR           Prefix where GL util library is installed (usually /usr)])
+
+have_glu=no
+include_glu=
+if test "$with_glu" != "no"; then
+  if test "$with_glu" = "yes" -o "$with_glu" = ""; then
+    AC_SEARCH_PACKAGE($gl_INCLUDE /usr /usr/local,,
+	              GL/glu.h, GLU, gluSphere, glu)
+  else
+    AC_SEARCH_PACKAGE($with_glu,,
+	              GL/glu.h, GLU, gluSphere, glu)
+  fi
+
+  if test "$with_glu" != ""; then
+    if test "$glu_PKG" != "yes"; then
+      dnl  If the user specified to search for GL util library but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate GL util library.  Use --with-glu=directory,"
+      echo "     e.g. --with-glu=/usr/local, or just --with-glu."
+      echo "     If GL util library is not installed, specify --without-glu."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$glu_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_GLU)
+#endif
+    have_glu=yes
+    if test "$glu_INCLUDE" != ""; then
+      glu_cflags="-I$glu_INCLUDE"
+    fi
+    if test "$glu_LIB" != ""; then
+      glu_lflags="-L$glu_LIB"
+    fi
+    glu_libs="-lGLU -lGLUU"
+  fi
+fi
+
+AC_SUBST(have_glu)
+AC_SUBST(glu_cflags)
+AC_SUBST(glu_lflags)
+AC_SUBST(glu_libs)
+AM_CONDITIONAL(HAVE_GLU, test "$have_glu" = "yes")
+
+
+AC_ARG_WITH(glx,
+[  --with-glx=DIR          Prefix where GLX is installed (usually /usr)])
+
+have_glx=no
+include_glx=
+if test "$with_glx" != "no"; then
+  if test "$with_glx" = "yes" -o "$with_glx" = ""; then
+    AC_SEARCH_HPACKAGE($gl_INCLUDE /usr /usr/local $x_libraries,,
+	               GL/glx.h, glx)
+  else
+    AC_SEARCH_HPACKAGE($with_glx,,
+	               GL/glx.h, glx)
+  fi
+
+  if test "$with_glx" != ""; then
+    if test "$glx_PKG" != "yes"; then
+      dnl  If the user specified to search for GLX but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate GLX package.  Use --with-glx=directory,"
+      echo "     e.g. --with-glx=/usr/local, or just --with-glx."
+      echo "     If GLX is not installed, specify --without-glx."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$glx_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_GLX)
+#endif
+    have_glx=yes
+    if test "$glx_INCLUDE" != ""; then
+      glx_cflags="-I$glx_INCLUDE"
+    fi
+  fi
+fi
+
+AC_SUBST(have_glx)
+AC_SUBST(glx_cflags)
+AM_CONDITIONAL(HAVE_GLX, test "$have_glx" = "yes")
+
+
+AC_ARG_WITH(glut,
+[  --with-glut=DIR         Prefix where glut is installed (usually /usr)])
+
+have_glut=no
+include_glut=
+if test "$with_glut" != "no"; then
+  if test "$with_glut" = "yes" -o "$with_glut" = ""; then
+    AC_SEARCH_PACKAGE($gl_INCLUDE /usr /usr/local $x_libraries,,
+	              GL/glut.h, glut, glutInit, glut, -lGL -lGLU)
+  else
+    AC_SEARCH_PACKAGE($with_glut,,
+	              GLUT/glut.h, glut, glutInit, glut, -lGL -lGLU)
+  fi
+
+  if test "$with_glut" != ""; then
+    if test "$glut_PKG" != "yes"; then
+      dnl  If the user specified to search for glut but we didn't find it,
+      dnl  abort now.
+
+      echo ""
+      echo "**** Could not locate glut package.  Use --with-glut=directory,"
+      echo "     e.g. --with-glut=/usr/local, or just --with-glut."
+      echo "     If glut is not installed, specify --without-glut."
+      echo ""
+      exit 1
+    fi
+  fi
+
+  if test "$glut_PKG" = "yes"; then
+#if $[ne $[CONFIG_HEADER],]
+    AC_DEFINE(HAVE_GLUT)
+#endif
+    have_glut=yes
+    if test "$glut_INCLUDE" != ""; then
+      glut_cflags="-I$glut_INCLUDE"
+    fi
+    if test "$glut_LIB" != ""; then
+      glut_lflags="-L$glut_LIB"
+    fi
+    glut_libs="-lglut"
+  fi
+fi
+
+AC_SUBST(have_glut)
+AC_SUBST(glut_cflags)
+AC_SUBST(glut_lflags)
+AC_SUBST(glut_libs)
+AM_CONDITIONAL(HAVE_GLUT, test "$have_glut" = "yes")
+
+
+AC_ARG_WITH(rib,
+[  --with-rib              Compile in the Renderman interface.])
+
+have_rib=no
+if test "$with_rib" = "yes"; then
+  have_rib=yes
+fi
+
+AC_SUBST(have_rib)
+#if $[ne $[CONFIG_HEADER],]
+if test "$have_rib" = "yes"; then
+AC_DEFINE(HAVE_RIB)
+fi
+#endif
+AM_CONDITIONAL(HAVE_RIB, test "$have_rib" = "yes")
+
+
+
+AC_ARG_WITH(mikmod,
+[  --with-mikmod[=libmikmod-config]  Use the mikmod interface for audio.])
+
+have_mikmod=no
+include_mikmod=
+if test "$with_mikmod" != "no"; then
+  if test "$with_mikmod" = "" -o "$with_mikmod" = "yes"; then
+    dnl search for the libmikmod-config program on the path.
+    AC_CHECK_PROG(with_mikmod, libmikmod-config, libmikmod-config, "no")
+  fi
+fi
+
+if test "$with_mikmod" != "no"; then
+  have_mikmod=yes
+  CFLAGS="$CFLAGS "`$with_mikmod --cflags`  
+  CXXFLAGS="$CXXFLAGS "`$with_mikmod --cflags`  
+  LDFLAGS="$LDFLAGS "`$with_mikmod --libs`  
+#if $[ne $[CONFIG_HEADER],]
+  AC_DEFINE(HAVE_MIKMOD)
+#endif
+fi
+
+AC_SUBST(have_mikmod)
+AM_CONDITIONAL(HAVE_MIKMOD, test "$have_mikmod" = "yes")
+
+ldflags=$LDFLAGS
+AC_SUBST(ldflags)
+
+AC_OUTPUT([$[TREE:%=%/Makefile]])
+
+#end configure.in
+
+//////////////////////////////////////////////////////////////////////
+#endif // DIR_TYPE

+ 721 - 0
ppremake/Template.stopgap.pp

@@ -0,0 +1,721 @@
+//
+// Template.stopgap.pp
+//
+// This file defines the set of output files that will be generated to
+// support our old-style Makefile system.  It is intended to aid as a
+// transition to the new system.
+//
+
+//////////////////////////////////////////////////////////////////////
+#if $[eq $[DIR_TYPE], src]
+//////////////////////////////////////////////////////////////////////
+
+// For a source directory, build a Makefile, Makefile.install, and a 
+// Makefile.target for each target.
+
+#define submakes $[TARGET(static_lib_target):%=%.a] $[TARGET(lib_target noinst_lib_target):%=%.so] $[TARGET(sed_bin_target bin_target noinst_bin_target test_bin_target)]
+#define install $[TARGET(static_lib_target):%=%.a] $[TARGET(lib_target noinst_lib_target):%=%.so] $[TARGET(sed_bin_target bin_target noinst_bin_target)]
+
+
+// This map variable lets us identify which metalib, if any, is
+// including each library built here.
+#map module COMPONENT_LIBS(*/metalib_target)
+
+// Now iterate through the libraries we're building and see which ones
+// actually *are* being included in a metalib.  For each one that is,
+// we install the appropriate deferred file.
+#define deferred
+#forscopes lib_target
+  #define metalib $[module $[TARGET],$[TARGET]]
+  #if $[ne $[metalib],]
+    #set deferred $[deferred] Deferred.$[metalib].lib$[TARGET].so
+  #endif
+#end lib_target
+
+// Get the full set of libraries we depend on.
+#call get_depend_libs
+
+// Also get the targets we'll be installing.
+#define install_libs $[sort $[TARGET(lib_target):%=lib%.so] $[TARGET(static_lib_target):%=lib%.a] $[INSTALL_LIBS]]
+#define install_bin $[sort $[TARGET(bin_target)] $[INSTALL_BIN]]
+#define install_scripts $[sort $[INSTALL_SCRIPTS(static_lib_target lib_target bin_target)] $[TARGET(sed_bin_target)] $[INSTALL_SCRIPTS]]
+#define install_headers $[sort $[INSTALL_HEADERS(static_lib_target lib_target bin_target)] $[INSTALL_HEADERS]]
+#define install_data $[sort $[INSTALL_DATA(static_lib_target lib_target sed_bin_target bin_target)] $[INSTALL_DATA]]
+
+// Collect the set of interrogate database files we'll install,
+// possibly one for each library we build.
+#define install_igatedb
+#if $[HAVE_PYTHON]
+  #forscopes lib_target
+    #if $[ne $[IGATESCAN],]
+      #set install_igatedb $[install_igatedb] lib$[TARGET].in
+    #endif
+  #end lib_target
+#endif
+
+
+#output Makefile
+#format makefile
+#### Meta Makefile.
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+
+#### Sub make targets (extension of sub Makefile, eg 'foo' for Makefile.foo):
+SUBMAKES = $[submakes]
+
+#### List the minimal set of sub makes on the list above required to install.
+INSTALL = $[install]
+
+#### Location of sub Makefiles.
+MAKEDIR = .
+
+#### The action is here.
+include $(DTOOL)/inc/Makefile.meta.rules
+
+#### Sub-make build order dependencies:
+# foo: bar
+#end Makefile
+
+
+
+#output Makefile.install
+#format makefile
+#### Installation makefile
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+# Note: This file is included by the project-wide Makefile so the current
+# directory is the project root.  Also, commented-out fields are optional.
+
+#### Package name and location (if not src/all/$(PACKAGE)):
+PACKAGE = $[DIRNAME]
+PKGROOT = $[PATH]
+
+ifneq (,$(PACKAGE))
+
+#### Package dependencies (USESOTHER needs relative paths from project root):
+USESLIBS = $[depend_libs]
+# USESINCLUDE = 
+# USESOTHER = 
+
+#### Installed files:
+LIBS = $[install_libs]
+DEFERRED = $[deferred]
+INCLUDE = $[install_headers]
+BINS = $[install_bin]
+SCRIPTS = $[install_scripts]
+# SS = 
+# STK = 
+# MODELS = 
+ETC = $[install_data]
+IGATEDB =$[install_igatedb]
+# DOC =
+# MAN =
+# FONTS = 
+# ICONS = 
+# APPDEFAULTS = 
+# TCL = 
+# TELEUSE = 
+# SHADERS = 
+
+#### Other files to be installed (use relative pathname from project root):
+#if $[ne $[INSTALL_PARSER_INC],]
+PARSER_INC = $[INSTALL_PARSER_INC]
+SRC_PARSER_INC = $(addprefix $(PKGROOT)/,$(PARSER_INC))
+INST_PARSER_INC = $(addprefix inc/parser-inc/,$(PARSER_INC))
+OTHER = $(INST_PARSER_INC)
+#else
+# OTHER =
+#endif
+
+#### Where the action happens.
+include $(DTOOL)/inc/Makefile.install.rules
+
+#### Install actions for OTHER files (source must be in $(PKGROOT)):
+# [ installed file ] : $(PKGROOT)/[ source file ]  # Files must have same name
+#	$(INSTALL)                      # Copies from source to dest
+#
+# [ installed file ] : $(PKGROOT)/[ source file ]
+#	$(MKINSTALL)			# Also makes directory if needed
+
+#if $[ne $[INSTALL_PARSER_INC],]
+$(INST_PARSER_INC) : inc/parser-inc/% : $(PKGROOT)/%
+	$(MKINSTALL)
+#endif
+
+#### Other install/uninstall actions:
+# install-$(PKGROOT): #Add dependencies here
+#	Add actions here
+#
+# uninstall-$(PKGROOT): #Add dependencies here
+#	Add actions here
+
+#### Sub-package Makefile.install inclusions:
+# include foo/Makefile.install
+
+endif
+#end Makefile.install
+
+
+
+// Now generate a suitable Makefile for each library target.
+#forscopes lib_target noinst_lib_target
+
+// Again, is this library included in a metalib?  If so, output the
+// appropriate deferred rules.
+#define metalib $[module $[TARGET],$[TARGET]]
+
+// We might need to define a BUILDING_ symbol for win32.  We use the
+// BUILDING_DLL variable name, defined typically in the metalib, for
+// this; but in some cases, where the library isn't part of a metalib,
+// we define BUILDING_DLL directly for the target.
+#define building_var $[BUILDING_DLL]
+#if $[ne $[metalib],]
+  #set building_var $[module $[BUILDING_DLL],$[TARGET]]
+#endif
+
+// Get the full set of sources for this target.
+#call get_sources
+#call get_libs
+
+// Which files will we interrogate, if any?
+#if $[HAVE_PYTHON]
+  #if $[ne $[IGATESCAN],]
+    #if $[eq $[IGATESCAN],all]
+      #define igatescan $[filter-out %.I %.lxx %.yxx %.N,$[sources]]
+    #else
+      #define igatescan $[IGATESCAN]
+    #endif
+  #endif
+#endif
+
+#output Makefile.$[TARGET].so
+#format makefile
+#### Makefile for DSO's.  Any fields commented out are optional.
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+#### Target's name:
+TARGET = lib$[TARGET].so
+DEFERRED_TARGET = $[metalib]
+
+# Standard .o file conversion information.
+
+#### Lex files 
+LFILES = $[filter %.lxx,$[sources]]
+LEX = flex
+LFLAGS = $[LFLAGS] $[YACC_PREFIX:%=-P%] -olex.yy.c
+LEXTENSION = cxx
+
+#### Yacc files 
+YFILES = $[filter %.yxx,$[sources]]
+YACC = bison
+YFLAGS = -y -d $[patsubst %,--name-prefix=%,$[YACC_PREFIX]]
+YEXTENSION = cxx
+
+#### C files 
+CFILES = $[filter %.c,$[sources]]
+CFLAGS = $[building_var:%=-D%] $[alt_cflags] $[CFLAGS]
+
+#### C++ files 
+C++FILES = $[filter %.cxx,$[sources]]
+C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
+# USETEMPLATES = TRUE
+# PTREPOSITORY = # Specify only if you want a specific name
+
+#### Interrogate info
+IGATESCAN  = $[igatescan]
+IGATEFLAGS = $[alt_ipath]
+# IGATEFILE  = # Specify only if you want a specific name
+
+#### Additional search directories for C/C++ header files:
+IPATH = $[alt_ipath]
+
+#### Location to put .o files:
+# ODIR = 
+
+#### Source file dependencies (unnecessary with clearmake)
+# foo.c: foo.h
+
+#### Other files and lib.  Include $(ODIR) in any .o names.
+# OFILES = 
+WHEN_NO_DEFER_LIBS = $[when_no_defer:%=-l%]
+WHEN_DEFER_LIBS = $[when_defer:%=-l%]
+LIBS = $[when_either:%=-l%]
+SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
+
+#### Additional search directories for lib:
+LPATH = $[alt_lpath]
+
+#### Other linker flags. 
+#if $[ne $[alt_ld],]
+LD = $[alt_ld]
+#endif
+# LDFLAGS = 
+
+#### Pull in standard .o make variables
+include $(DTOOL)/inc/Makefile.o.vars
+
+#### The .o action is here.
+include $(DTOOL)/inc/Makefile.o.rules
+
+#### Pull in standard binary make variables.
+include $(DTOOL)/inc/Makefile.bin.vars
+
+#### The .so action is here.
+include $(DTOOL)/inc/Makefile.so.rules
+#end Makefile.$[TARGET].so
+
+#end lib_target noinst_lib_target
+
+
+
+// Also generate a suitable Makefile for each static library target.
+#forscopes static_lib_target
+
+// Get the full set of sources for this target.
+#call get_sources
+#call get_libs
+
+#output Makefile.$[TARGET].a
+#format makefile
+#### Makefile for archive libraries.  Any fields commented out are optional.
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+#### Target's name:
+TARGET = lib$[TARGET].a
+
+# Standard .o file conversion information.
+
+#### Lex files 
+LFILES = $[filter %.lxx,$[sources]]
+LEX = flex
+LFLAGS = $[LFLAGS] $[YACC_PREFIX:%=-P%] -olex.yy.c
+LEXTENSION = yy.cxx
+# LSUBST =
+
+#### Yacc files 
+YFILES = $[filter %.yxx,$[sources]]
+YACC = bison
+YFLAGS = -y -d $[patsubst %,--name-prefix=%,$[YACC_PREFIX]]
+YEXTENSION = tab.cxx
+# YSUBST =
+
+#### C files 
+CFILES = $[filter %.c,$[sources]]
+CFLAGS = $[building_var:%=-D%] $[alt_cflags] $[CFLAGS]
+
+#### C++ files 
+C++FILES = $[filter %.cxx,$[sources]]
+C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
+# USETEMPLATES = TRUE
+# PTREPOSITORY = # Specify only if you want a specific name
+
+#### Additional search directories for C/C++ header files:
+IPATH = $[alt_ipath]
+
+#### Location to put .o files:
+# ODIR = 
+
+#### Source file dependencies (unnecessary with clearmake)
+# foo.c: foo.h
+
+#### Other .o files.
+# OFILES = 
+
+#### Libs and flags for template instantiation.
+WHEN_NO_DEFER_LIBS = $[when_no_defer:%=-l%]
+WHEN_DEFER_LIBS = $[when_defer:%=-l%]
+LIBS = $[when_either:%=-l%]
+SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
+
+#### Additional search directories for lib:
+LPATH = $[alt_lpath]
+
+#### Archiver flags
+# ARFLAGS = 
+
+#### Pull in standard .o make variables
+include $(DTOOL)/inc/Makefile.o.vars
+
+#### The .o action is here.
+include $(DTOOL)/inc/Makefile.o.rules
+
+#### Pull in standard binary make variables.
+include $(DTOOL)/inc/Makefile.bin.vars
+
+#### The .a action is here.
+include $(DTOOL)/inc/Makefile.a.rules
+#end Makefile.$[TARGET].a
+
+#end static_lib_target
+
+
+
+// And also generate a suitable Makefile for each binary target.
+#forscopes bin_target noinst_bin_target test_bin_target
+
+// Get the full set of sources for this target.
+#call get_sources
+#call get_libs
+
+#output Makefile.$[TARGET]
+#format makefile
+#### Makefile for binaries.  Any fields commented out are optional.
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+#### Target's name:
+TARGET = $[TARGET]
+
+# Standard .o file conversion information.
+
+#### Lex files 
+LFILES = $[filter %.lxx,$[sources]]
+LEX = flex
+LFLAGS = $[LFLAGS] $[YACC_PREFIX:%=-P%] -olex.yy.c
+LEXTENSION = yy.cxx
+# LSUBST =
+
+#### Yacc files 
+YFILES = $[filter %.yxx,$[sources]]
+YACC = bison
+YFLAGS = -y -d $[patsubst %,--name-prefix=%,$[YACC_PREFIX]]
+YEXTENSION = tab.cxx
+# YSUBST =
+
+#### C files 
+CFILES = $[filter %.c,$[sources]]
+CFLAGS = $[building_var:%=-D%] $[alt_cflags] $[CFLAGS]
+
+#### C++ files 
+C++FILES = $[filter %.cxx,$[sources]]
+C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
+
+#### Additional search directories for C/C++ header files:
+IPATH = $[alt_ipath]
+
+#### Location to put .o files:
+# ODIR = 
+
+#### Source file dependencies (unnecessary with clearmake)
+# foo.c: foo.h
+
+#### Other files and lib.  Include $(ODIR) in any .o names.
+# OFILES = 
+WHEN_NO_DEFER_LIBS = $[when_no_defer:%=-l%]
+WHEN_DEFER_LIBS = $[when_defer:%=-l%]
+LIBS = $[when_either:%=-l%]
+SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
+
+#### Additional search directories for lib:
+LPATH = $[alt_lpath]
+
+#### Other linker flags. 
+#if $[ne $[alt_ld],]
+LD = $[alt_ld]
+#endif
+# LDFLAGS =
+
+#### Pull in standard .o make variables
+include $(DTOOL)/inc/Makefile.o.vars
+
+#### The .o action is here.
+include $(DTOOL)/inc/Makefile.o.rules
+
+#### Pull in standard binary make variables.
+include $(DTOOL)/inc/Makefile.bin.vars
+
+#### The bin action is here.
+include $(DTOOL)/inc/Makefile.bin.rules
+#end Makefile.$[TARGET]
+
+#end bin_target noinst_bin_target test_bin_target
+
+
+// Finally, generate the special scripts from the sed_bin_targets.  Hopefully
+// there won't be too many of these in the tree, since these are fairly
+// Unix-specific.
+#forscopes sed_bin_target
+#output Makefile.$[TARGET]
+#format makefile
+#### This is a special makefile just to generate the $[TARGET] script.
+
+$[TARGET] : $[SOURCE]
+	sed $[COMMAND] $^ >$@
+	chmod +x $@
+
+clean :
+
+cleanall :
+	rm -f $[TARGET]
+#end Makefile.$[TARGET]
+#end sed_bin_target
+
+
+//////////////////////////////////////////////////////////////////////
+#elif $[eq $[DIR_TYPE], metalib]
+//////////////////////////////////////////////////////////////////////
+
+// A metalib directory is similar to a regular source directory, 
+// but a little simpler.
+
+#define submakes $[TARGET(metalib_target):%=%.so]
+
+
+// This map variable lets us identify which metalib, if any, is
+// including each library built here.
+#map module COMPONENT_LIBS(*/metalib_target)
+
+// Get the full set of libraries we depend on.
+#call get_depend_libs
+
+// Also get the targets we'll be installing.
+#define install_libs $[TARGET(metalib_target):%=lib%.so]
+#define install_headers $[INSTALL_HEADERS(metalib_target)]
+#define install_data $[INSTALL_DATA(metalib_target)]
+
+
+#output Makefile
+#format makefile
+#### Meta Makefile.
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+
+#### Sub make targets (extension of sub Makefile, eg 'foo' for Makefile.foo):
+SUBMAKES = $[submakes]
+
+#### List the minimal set of sub makes on the list above required to install.
+INSTALL = $[submakes]
+
+#### Location of sub Makefiles.
+MAKEDIR = .
+
+#### The action is here.
+include $(DTOOL)/inc/Makefile.meta.rules
+
+#### Sub-make build order dependencies:
+# foo: bar
+#end Makefile
+
+
+
+#output Makefile.install
+#format makefile
+#### Installation makefile
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+# Note: This file is included by the project-wide Makefile so the current
+# directory is the project root.  Also, commented-out fields are optional.
+
+#### Package name and location (if not src/all/$(PACKAGE)):
+PACKAGE = $[DIRNAME]
+PKGROOT = $[PATH]
+
+ifneq (,$(PACKAGE))
+
+#### Package dependencies (USESOTHER needs relative paths from project root):
+USESLIBS = $[depend_libs]
+# USESINCLUDE = 
+# USESOTHER = 
+
+#### Installed files:
+LIBS = $[install_libs]
+INCLUDE = $[install_headers]
+# BINS =
+# SS = 
+# STK = 
+# MODELS = 
+# ETC =
+# DOC =
+# MAN =
+# FONTS = 
+# ICONS = 
+# APPDEFAULTS = 
+# TCL = 
+# TELEUSE = 
+# SHADERS = 
+
+#### Other files to be installed (use relative pathname from project root):
+# OTHER = 
+
+#### Where the action happens.
+include $(DTOOL)/inc/Makefile.install.rules
+
+#### Install actions for OTHER files (source must be in $(PKGROOT)):
+# [ installed file ] : $(PKGROOT)/[ source file ]  # Files must have same name
+#	$(INSTALL)                      # Copies from source to dest
+#
+# [ installed file ] : $(PKGROOT)/[ source file ]
+#	$(MKINSTALL)			# Also makes directory if needed
+
+#### Other install/uninstall actions:
+# install-$(PKGROOT): #Add dependencies here
+#	Add actions here
+#
+# uninstall-$(PKGROOT): #Add dependencies here
+#	Add actions here
+
+#### Sub-package Makefile.install inclusions:
+# include foo/Makefile.install
+
+endif
+#end Makefile.install
+
+// Now generate a suitable Makefile for each metalib target.
+#forscopes metalib_target
+
+#define building_var $[BUILDING_DLL]
+
+// Get the full set of sources for this target.
+#call get_sources
+#call get_libs
+
+#if $[HAVE_PYTHON]
+  #map components TARGET(*/lib_target */noinst_lib_target)
+  #if $[ne $[components $[IGATESCAN],$[COMPONENT_LIBS]],]
+    #define igatemscan $[TARGET]
+  #endif
+#endif
+
+#output Makefile.$[TARGET].so
+#format makefile
+#### Makefile for DSO's.  Any fields commented out are optional.
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+#### Target's name:
+TARGET = lib$[TARGET].so
+
+# Standard .o file conversion information.
+
+#### Lex files 
+LFILES = $[filter %.lxx,$[sources]]
+LEX = flex
+LFLAGS = $[LFLAGS] $[YACC_PREFIX:%=-P%] -olex.yy.c
+LEXTENSION = yy.cxx
+# LSUBST =
+
+#### Yacc files 
+YFILES = $[filter %.yxx,$[sources]]
+YACC = bison
+YFLAGS = -y -d $[patsubst %,--name-prefix=%,$[YACC_PREFIX]]
+YEXTENSION = tab.cxx
+# YSUBST =
+
+#### C files 
+CFILES = $[filter %.c,$[sources]]
+CFLAGS = $[building_var:%=-D%] $[alt_cflags] $[CFLAGS]
+
+#### C++ files 
+C++FILES = $[filter %.cxx,$[sources]]
+C++FLAGS = $[building_var:%=-D%] $[alt_cflags] $[C++FLAGS]
+# USETEMPLATES = TRUE
+# PTREPOSITORY = # Specify only if you want a specific name
+
+#### Interrogate info
+# IGATESCAN  = 
+# IGATEFLAGS = 
+# IGATEFILE  = # Specify only if you want a specific name
+IGATEMSCAN = $[igatemscan]
+
+#### Pull in deferred-target files built in other packages
+DEFERRED_FILES = $[TARGET]
+
+#### Additional search directories for C/C++ header files:
+IPATH = $[alt_ipath]
+
+#### Location to put .o files:
+# ODIR = 
+
+#### Source file dependencies (unnecessary with clearmake)
+# foo.c: foo.h
+
+#### Other files and lib.  Include $(ODIR) in any .o names.
+# OFILES = 
+WHEN_NO_DEFER_LIBS = $[when_no_defer:%=-l%]
+WHEN_DEFER_LIBS = $[when_defer:%=-l%]
+LIBS = $[when_either:%=-l%]
+SYSLIBS = $[patsubst %.lib,%.lib,%,-l%,$[unique $[alt_libs]]]
+
+#### Additional search directories for lib:
+LPATH = $[alt_lpath]
+
+#### Other linker flags. 
+#if $[ne $[alt_ld],]
+LD = $[alt_ld]
+#endif
+# LDFLAGS = 
+
+#### Pull in standard .o make variables
+include $(DTOOL)/inc/Makefile.o.vars
+
+#### The .o action is here.
+include $(DTOOL)/inc/Makefile.o.rules
+
+#### Pull in standard binary make variables.
+include $(DTOOL)/inc/Makefile.bin.vars
+
+#### The .so action is here.
+include $(DTOOL)/inc/Makefile.so.rules
+#end Makefile.$[TARGET].so
+
+#end metalib_target
+
+
+//////////////////////////////////////////////////////////////////////
+#elif $[eq $[DIR_TYPE], group]
+//////////////////////////////////////////////////////////////////////
+
+// This is a group directory: a directory above a collection of source
+// directories, e.g. $DTOOL/src.  We don't need to output anything in
+// this directory.
+
+
+
+//////////////////////////////////////////////////////////////////////
+#elif $[eq $[DIR_TYPE], toplevel]
+//////////////////////////////////////////////////////////////////////
+
+// This is the toplevel directory, e.g. $DTOOL.  Here we build the
+// root makefile and also synthesize the dtool_config.h (or whichever
+// file) we need.
+
+#output Makefile
+#format makefile
+#### Generated automatically by $[PROGRAM] $[PROGVER] from $[SOURCEFILE].
+################################# DO NOT EDIT ###########################
+
+# Specify project name and project root directory.
+CTPROJECT = $[PACKAGE]
+CTPROJROOT = $($[upcase $[PACKAGE]])
+
+include $(DTOOL)/inc/Makefile.project.vars
+
+// Iterate through all of our known source files.  Each src and
+// metalib type file gets its corresponding Makefile.install listed
+// here.  However, we test for $[DIR_TYPE] of toplevel, because the
+// source directories typically don't define their own DIR_TYPE
+// variable, and they end up inheriting this one dynamically.
+#forscopes */
+#if $[or $[eq $[DIR_TYPE], src],$[eq $[DIR_TYPE], metalib],$[and $[eq $[DIR_TYPE], toplevel],$[ne $[DIRNAME],top]]]
+#if $[build_directory]
+include $[PATH]/Makefile.install
+#endif
+#endif
+#end */
+
+#end Makefile
+
+// If there is a file called LocalSetup.pp in the package's top
+// directory, then invoke that.  It might contain some further setup
+// instructions.
+#sinclude $[TOPDIRPREFIX]LocalSetup.stopgap.pp
+#sinclude $[TOPDIRPREFIX]LocalSetup.pp
+
+//////////////////////////////////////////////////////////////////////
+#endif // DIR_TYPE

+ 39 - 0
ppremake/acconfig.h

@@ -0,0 +1,39 @@
+/* acconfig.h
+   This file is in the public domain.
+
+   Descriptive text for the C preprocessor macros that
+   the distributed Autoconf macros can define.
+   No software package will use all of them; autoheader copies the ones
+   your configure.in uses into your configuration header file templates.
+
+   The entries are in sort -df order: alphabetical, case insensitive,
+   ignoring punctuation (such as underscores).  Although this order
+   can split up related entries, it makes it easier to check whether
+   a given entry is in the file.
+
+   Leave the following blank line there!!  Autoheader needs it.  */
+
+
+/* Define if the C++ compiler uses namespaces.  */
+#undef HAVE_NAMESPACE
+
+/* Define if the C++ iostream library supports ios::binary.  */
+#undef HAVE_IOS_BINARY
+
+/* Define if we're compiling for a Windows platform.  */
+#undef PLATFORM_WIN32
+
+/* The current version number. */
+#define VERSION 0.0
+
+/* The platform ppremake is compiled for.  This primarily controls the
+   initial setting of the $[PLATFORM] variable. */
+#define PLATFORM ""
+
+
+/* Leave that blank line there!!  Autoheader needs it.
+   If you're adding to this file, keep in mind:
+   The entries are in sort -df order: alphabetical, case insensitive,
+   ignoring punctuation (such as underscores).  */
+
+

+ 405 - 0
ppremake/acinclude.m4

@@ -0,0 +1,405 @@
+AC_DEFUN(AC_HEADER_IOSTREAM,
+[AC_CHECK_HEADERS(iostream,[have_iostream=yes],[have_iostream=no])])
+
+AC_DEFUN(AC_IOS_BINARY,
+[AC_CACHE_CHECK([for ios::binary],
+  ac_cv_ios_binary,
+[
+if test $have_iostream = yes; then
+  AC_TRY_COMPILE([
+#include <iostream>
+  ],[
+  int x; x = ios::binary;
+  ], ac_cv_ios_binary=yes, ac_cv_ios_binary=no)
+else
+  AC_TRY_COMPILE([
+#include <iostream.h>
+  ],[
+  int x; x = ios::binary;
+  ], ac_cv_ios_binary=yes, ac_cv_ios_binary=no)
+fi
+
+])
+if test $ac_cv_ios_binary = yes; then
+  AC_DEFINE(HAVE_IOS_BINARY)
+fi
+])
+
+
+AC_DEFUN(AC_NAMESPACE,
+[AC_CACHE_CHECK([for compiler namespace support],
+  ac_cv_namespace,
+[AC_TRY_COMPILE(
+[namespace std { };
+using namespace std;],
+[],
+  ac_cv_namespace=yes, ac_cv_namespace=no)])
+if test $ac_cv_namespace = yes; then
+  AC_DEFINE(HAVE_NAMESPACE)
+fi
+])
+
+
+dnl A handy function to see if a library is in a particular directory.
+dnl AC_CHECK_LIB_LOC(directory, library, function, action-if-found, action-if-not-found, other-libraries)
+dnl
+AC_DEFUN(AC_CHECK_LIB_LOC,
+[AC_MSG_CHECKING([for lib$2 in $1])
+ac_lib_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'`
+AC_CACHE_VAL(ac_cv_lib_loc_$ac_lib_var,
+[ac_save_LIBS="$LIBS"
+LIBS="-L$1 -l$2 $6 $LIBS"
+AC_TRY_LINK(dnl
+ifelse(AC_LANG, CPLUSPLUS, [#ifdef __cplusplus
+extern "C"
+#endif
+])dnl
+[/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char $3();
+],
+            [$3()],
+            eval "ac_cv_lib_loc_$ac_lib_var=yes",
+            eval "ac_cv_lib_loc_$ac_lib_var=no")
+LIBS="$ac_save_LIBS"
+])dnl
+if eval "test \"`echo '$ac_cv_lib_loc_'$ac_lib_var`\" = yes"; then
+  AC_MSG_RESULT(yes)
+  ifelse([$4], ,
+[LIBS="-L$1 -l$2 $LIBS"
+], [$4])
+else
+  AC_MSG_RESULT(no)
+ifelse([$5], , , [$5
+])dnl
+fi
+])
+
+dnl A handy function to search a number of possible locations for a library.
+dnl AC_SEARCH_LIB(search-dirs, library, function, package, other-libraries)
+dnl 
+dnl Sets $package_LIB to the directory containing the library, or to the
+dnl empty string if the library cannot be found.
+AC_DEFUN(AC_SEARCH_LIB, [
+ac_found_lib=""
+for ac_check_dir in $1; do
+  if test "$ac_found_lib" = ""; then
+    AC_CHECK_LIB_LOC($ac_check_dir, $2, $3, [ ac_found_lib="$ac_check_dir"; ],, $5)
+  fi
+done
+$4_LIB="$ac_found_lib"
+])
+
+dnl A handy function to see if a header file is in a particular directory.
+dnl AC_CHECK_HEADER_LOC(directory, header, action-if-found, action-if-not-found)
+dnl
+AC_DEFUN(AC_CHECK_HEADER_LOC, [
+  AC_MSG_CHECKING([for $2 in $1])
+  ac_include_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'`
+  AC_CACHE_VAL(ac_cv_include_loc_$ac_include_var, [
+    ac_save_CPPFLAGS="$CPPFLAGS"
+    CPPFLAGS="-I$1 $CPPFLAGS"
+    AC_TRY_CPP([#include <$2>], ac_ch_found_it="yes", ac_ch_found_it="no")
+    if test "$ac_ch_found_it" = "yes"; then
+      AC_MSG_RESULT(yes)
+      ifelse([$3], , :, [$3])
+    else
+      AC_MSG_RESULT(no)
+      ifelse([$4], , , [$4])
+    fi
+    CPPFLAGS="$ac_save_CPPFLAGS"
+  ])
+])
+
+
+dnl A handy function to search a number of possible locations for a header
+dnl file.
+dnl
+dnl AC_SEARCH_HEADER(search-dirs, header, package)
+dnl
+dnl Sets $package_INCLUDE to the directory containing the header, or to
+dnl the empty string if the header cannot be found.
+AC_DEFUN(AC_SEARCH_HEADER, [
+ac_found_header=""
+for ac_check_dir in $1; do
+  if test "$ac_found_header" = ""; then
+    AC_CHECK_HEADER_LOC($ac_check_dir, $2, [ ac_found_header="$ac_check_dir";])
+  fi
+done
+$3_INCLUDE="$ac_found_header"
+])
+
+
+dnl A handy function to scan for a third-party package, consisting of at
+dnl least a library and an include file.  A few assumptions are made about
+dnl the relationships between lib and include directories.
+dnl
+dnl AC_SEARCH_PACKAGE(search-dirs, package-names, header, library, function, package, other-libraries)
+dnl
+dnl search-dirs is the whitespace-separated list of directory prefixes to
+dnl   check.
+dnl package-names is a whitespace-separated list of possible names the
+dnl   package may have been installed under.
+dnl
+dnl For each combination of ${search-dir} and ${package-name}, the following
+dnl directories are searched:
+dnl
+dnl   ${search-dir}
+dnl   ${search-dir}/lib
+dnl   ${search-dir}/${package-name}
+dnl   ${search-dir}/${package-name}/lib
+dnl   ${search-dir}/lib/${package-name}
+dnl
+dnl And similarly for include.
+dnl
+dnl Sets the variables $package_INCLUDE and $package_LIB to the directories
+dnl containing the header file and library, respectively.  If both pieces
+dnl are located, also sets the variable $package_PKG to "yes"; otherwise,
+dnl sets the variable $package_PKG to "no".
+dnl
+AC_DEFUN(AC_SEARCH_PACKAGE, [
+$6_LIB=""
+$6_INCLUDE=""
+$6_PKG="no"
+
+dnl Look for the library.
+for ac_sp_dir in $1; do
+  if test "[$]$6_LIB" = ""; then
+    AC_SEARCH_LIB("$ac_sp_dir" "$ac_sp_dir/lib", $4, $5, $6, $7)
+    for ac_sp_pkg in $2; do
+      if test "[$]$6_LIB" = ""; then
+        AC_SEARCH_LIB("$ac_sp_dir/$ac_sp_pkg" "$ac_sp_dir/$ac_sp_pkg/lib" \
+                      "$ac_sp_dir/lib/$ac_sp_pkg", $4, $5, $6, $7)
+      fi
+    done
+  fi
+done
+
+dnl Now look for the header file.  Don't bother looking if the library
+dnl wasn't found.
+if test "[$]$6_LIB" != ""; then
+  dnl First look in the obvious directory corresponding to the lib dir.
+  ac_sp_testinc=`echo [$]$6_LIB | sed 's:/lib:/include:'`
+  AC_SEARCH_HEADER("$ac_sp_testinc", $3, $6)
+
+  dnl If it wasn't found there, cast about.
+  if test "[$]$6_INCLUDE" = ""; then
+    for ac_sp_dir in $1; do
+      if test "[$]$6_INCLUDE" = ""; then
+        AC_SEARCH_HEADER("$ac_sp_dir" "$ac_sp_dir/include", $3, $6)
+        for ac_sp_pkg in $2; do
+          if test "[$]$6_INCLUDE" = ""; then
+            AC_SEARCH_HEADER("$ac_sp_dir/$ac_sp_pkg" \
+                             "$ac_sp_dir/$ac_sp_pkg/include" \
+                             "$ac_sp_dir/include/$ac_sp_pkg", $3, $6)
+          fi
+        done
+      fi
+    done
+  fi
+
+  dnl If we got both a header and a library, set the PKG variable.
+  if test "[$]$6_INCLUDE" != ""; then
+    $6_PKG="yes"
+  fi
+fi
+])  
+
+
+
+
+# Configure paths for GTK--
+# Erik Andersen	30 May 1998
+# Modified by Tero Pulkkinen (added the compiler checks... I hope they work..)
+
+dnl Test for GTKMM, and define GTKMM_CFLAGS and GTKMM_LIBS
+dnl   to be used as follows:
+dnl AM_PATH_GTKMM([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]])
+dnl
+AC_DEFUN(AM_PATH_GTKMM,
+[dnl 
+dnl Get the cflags and libraries from the gtkmm-config script
+dnl
+AC_ARG_WITH(gtkmm-prefix,[  --with-gtkmm-prefix=PREFIX
+                          Prefix where GTK-- is installed (optional)],
+            gtkmm_config_prefix="$withval", gtkmm_config_prefix="")
+AC_ARG_WITH(gtkmm-exec-prefix,[  --with-gtkmm-exec-prefix=PREFIX
+                          Exec prefix where GTK-- is installed (optional)],
+            gtkmm_config_exec_prefix="$withval", gtkmm_config_exec_prefix="")
+AC_ARG_ENABLE(gtkmmtest, [  --disable-gtkmmtest     Do not try to compile and run a test GTK-- program],
+		    , enable_gtkmmtest=yes)
+
+  if test x$gtkmm_config_exec_prefix != x ; then
+     gtkmm_config_args="$gtkmm_config_args --exec-prefix=$gtkmm_config_exec_prefix"
+     if test x${GTKMM_CONFIG+set} != xset ; then
+        GTKMM_CONFIG=$gtkmm_config_exec_prefix/bin/gtkmm-config
+     fi
+  fi
+  if test x$gtkmm_config_prefix != x ; then
+     gtkmm_config_args="$gtkmm_config_args --prefix=$gtkmm_config_prefix"
+     if test x${GTKMM_CONFIG+set} != xset ; then
+        GTKMM_CONFIG=$gtkmm_config_prefix/bin/gtkmm-config
+     fi
+  fi
+
+  AC_PATH_PROG(GTKMM_CONFIG, gtkmm-config, no)
+  min_gtkmm_version=ifelse([$1], ,0.10.0,$1)
+
+  AC_MSG_CHECKING(for GTK-- - version >= $min_gtkmm_version)
+  no_gtkmm=""
+  if test "$GTKMM_CONFIG" = "no" ; then
+    no_gtkmm=yes
+  else
+    AC_LANG_SAVE
+    AC_LANG_CPLUSPLUS
+
+    GTKMM_CFLAGS=`$GTKMM_CONFIG $gtkmm_config_args --cflags`
+    GTKMM_LIBS=`$GTKMM_CONFIG $gtkmm_config_args --libs`
+    gtkmm_config_major_version=`$GTKMM_CONFIG $gtkmm_config_args --version | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+    gtkmm_config_minor_version=`$GTKMM_CONFIG $gtkmm_config_args --version | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+    gtkmm_config_micro_version=`$GTKMM_CONFIG $gtkmm_config_args --version | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+    if test "x$enable_gtkmmtest" = "xyes" ; then
+      ac_save_CXXFLAGS="$CXXFLAGS"
+      ac_save_LIBS="$LIBS"
+      CXXFLAGS="$CXXFLAGS $GTKMM_CFLAGS"
+      LIBS="$LIBS $GTKMM_LIBS"
+dnl
+dnl Now check if the installed GTK-- is sufficiently new. (Also sanity
+dnl checks the results of gtkmm-config to some extent
+dnl
+      rm -f conf.gtkmmtest
+      AC_TRY_RUN([
+#include <gtk--.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int 
+main ()
+{
+  int major, minor, micro;
+  char *tmp_version;
+
+  system ("touch conf.gtkmmtest");
+
+  /* HP/UX 0 (%@#!) writes to sscanf strings */
+  tmp_version = g_strdup("$min_gtkmm_version");
+  if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, &micro) != 3) {
+     printf("%s, bad version string\n", "$min_gtkmm_version");
+     exit(1);
+   }
+
+  if ((gtkmm_major_version != $gtkmm_config_major_version) ||
+      (gtkmm_minor_version != $gtkmm_config_minor_version) ||
+      (gtkmm_micro_version != $gtkmm_config_micro_version))
+    {
+      printf("\n*** 'gtkmm-config --version' returned %d.%d.%d, but GTK-- (%d.%d.%d)\n", 
+             $gtkmm_config_major_version, $gtkmm_config_minor_version, $gtkmm_config_micro_version,
+             gtkmm_major_version, gtkmm_minor_version, gtkmm_micro_version);
+      printf ("*** was found! If gtkmm-config was correct, then it is best\n");
+      printf ("*** to remove the old version of GTK--. You may also be able to fix the error\n");
+      printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n");
+      printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n");
+      printf("*** required on your system.\n");
+      printf("*** If gtkmm-config was wrong, set the environment variable GTKMM_CONFIG\n");
+      printf("*** to point to the correct copy of gtkmm-config, and remove the file config.cache\n");
+      printf("*** before re-running configure\n");
+    } 
+/* GTK-- does not have the GTKMM_*_VERSION constants */
+/* 
+  else if ((gtkmm_major_version != GTKMM_MAJOR_VERSION) ||
+	   (gtkmm_minor_version != GTKMM_MINOR_VERSION) ||
+           (gtkmm_micro_version != GTKMM_MICRO_VERSION))
+    {
+      printf("*** GTK-- header files (version %d.%d.%d) do not match\n",
+	     GTKMM_MAJOR_VERSION, GTKMM_MINOR_VERSION, GTKMM_MICRO_VERSION);
+      printf("*** library (version %d.%d.%d)\n",
+	     gtkmm_major_version, gtkmm_minor_version, gtkmm_micro_version);
+    }
+*/
+  else
+    {
+      if ((gtkmm_major_version > major) ||
+        ((gtkmm_major_version == major) && (gtkmm_minor_version > minor)) ||
+        ((gtkmm_major_version == major) && (gtkmm_minor_version == minor) && (gtkmm_micro_version >= micro)))
+      {
+        return 0;
+       }
+     else
+      {
+        printf("\n*** An old version of GTK-- (%d.%d.%d) was found.\n",
+               gtkmm_major_version, gtkmm_minor_version, gtkmm_micro_version);
+        printf("*** You need a version of GTK-- newer than %d.%d.%d. The latest version of\n",
+	       major, minor, micro);
+        printf("*** GTK-- is always available from ftp://ftp.gtk.org.\n");
+        printf("***\n");
+        printf("*** If you have already installed a sufficiently new version, this error\n");
+        printf("*** probably means that the wrong copy of the gtkmm-config shell script is\n");
+        printf("*** being found. The easiest way to fix this is to remove the old version\n");
+        printf("*** of GTK--, but you can also set the GTKMM_CONFIG environment to point to the\n");
+        printf("*** correct copy of gtkmm-config. (In this case, you will have to\n");
+        printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n");
+        printf("*** so that the correct libraries are found at run-time))\n");
+      }
+    }
+  return 1;
+}
+],, no_gtkmm=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
+       CXXFLAGS="$ac_save_CXXFLAGS"
+       LIBS="$ac_save_LIBS"
+     fi
+  fi
+  if test "x$no_gtkmm" = x ; then
+     AC_MSG_RESULT(yes)
+     ifelse([$2], , :, [$2])     
+  else
+     AC_MSG_RESULT(no)
+     if test "$GTKMM_CONFIG" = "no" ; then
+       echo "*** The gtkmm-config script installed by GTK-- could not be found"
+       echo "*** If GTK-- was installed in PREFIX, make sure PREFIX/bin is in"
+       echo "*** your path, or set the GTKMM_CONFIG environment variable to the"
+       echo "*** full path to gtkmm-config."
+       echo "*** The gtkmm-config script was not available in GTK-- versions"
+       echo "*** prior to 0.9.12. Perhaps you need to update your installed"
+       echo "*** version to 0.9.12 or later"
+     else
+       if test -f conf.gtkmmtest ; then
+        :
+       else
+          echo "*** Could not run GTK-- test program, checking why..."
+          CXXFLAGS="$CFLAGS $GTKMM_CXXFLAGS"
+          LIBS="$LIBS $GTKMM_LIBS"
+          AC_TRY_LINK([
+#include <gtk--.h>
+#include <stdio.h>
+],      [ return ((gtkmm_major_version) || (gtkmm_minor_version) || (gtkmm_micro_version)); ],
+        [ echo "*** The test program compiled, but did not run. This usually means"
+          echo "*** that the run-time linker is not finding GTK-- or finding the wrong"
+          echo "*** version of GTK--. If it is not finding GTK--, you'll need to set your"
+          echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+          echo "*** to the installed location  Also, make sure you have run ldconfig if that"
+          echo "*** is required on your system"
+	  echo "***"
+          echo "*** If you have an old version installed, it is best to remove it, although"
+          echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" ],
+        [ echo "*** The test program failed to compile or link. See the file config.log for the"
+          echo "*** exact error that occured. This usually means GTK-- was incorrectly installed"
+          echo "*** or that you have moved GTK-- since it was installed. In the latter case, you"
+          echo "*** may want to edit the gtkmm-config script: $GTKMM_CONFIG" ])
+          CXXFLAGS="$ac_save_CXXFLAGS"
+          LIBS="$ac_save_LIBS"
+       fi
+     fi
+     GTKMM_CFLAGS=""
+     GTKMM_LIBS=""
+     ifelse([$3], , :, [$3])
+     AC_LANG_RESTORE
+  fi
+  AC_SUBST(GTKMM_CFLAGS)
+  AC_SUBST(GTKMM_LIBS)
+  rm -f conf.gtkmmtest
+])
+
+

+ 76 - 0
ppremake/configure.in

@@ -0,0 +1,76 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(ppremake.cxx)
+AM_INIT_AUTOMAKE(ppremake, 0.25a)
+AM_CONFIG_HEADER(config.h)
+
+AC_PREFIX_DEFAULT(/usr/local/panda)
+AC_PROG_MAKE_SET
+AC_CANONICAL_HOST
+
+
+# If we have a CFLAGS variable but not a CXXFLAGS variable, let them
+# be the same.
+if test "${CXXFLAGS+set}" != set -a "${CFLAGS+set}" = set; then
+  CXXFLAGS=$CFLAGS
+fi
+
+# Save these variables for later, so we can easily append to them or
+# change them.
+user_ldflags=${LDFLAGS-}
+user_cflags=${CFLAGS-}
+user_cxxflags=${CXXFLAGS-}
+
+dnl Checks for programs.
+AC_PROG_CC
+AC_PROG_CXX
+
+dnl First, we'll test for C-specific features.
+AC_LANG_C
+
+dnl Checks for libraries.
+libdl=
+libm=
+AC_CHECK_LIB(m, sin, libm=-lm)
+AC_SUBST(libm)
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(malloc.h alloca.h unistd.h io.h minmax.h sys/types.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+
+dnl Checks for library functions.
+AC_CHECK_FUNCS(getopt)
+
+
+dnl Now we can test some C++-specific features.
+AC_LANG_CPLUSPLUS
+AC_HEADER_IOSTREAM
+AC_NAMESPACE
+
+
+AC_LANG_C
+
+AC_ARG_WITH(platform,
+[  --with-platform=platform  Set the $[PLATFORM] predefined variable.])
+
+if test "${with_platform-no}" != "no"; then
+  PLATFORM=$with_platform
+else
+  case "$host_os" in
+    irix*) PLATFORM=Irix;;
+    linux*) PLATFORM=Linux;;
+    cygwin*) PLATFORM=Win32;;
+    *) echo "Cannot determine platform; use --with-platform=name."
+       exit 1;;
+  esac
+fi
+
+if test "$PLATFORM" = "Win32"; then
+  AC_DEFINE(PLATFORM_WIN32)
+fi
+
+AC_DEFINE_UNQUOTED(PLATFORM, "$PLATFORM")
+
+
+AC_OUTPUT(Makefile)

+ 22 - 0
ppremake/find_searchpath.cxx

@@ -0,0 +1,22 @@
+// Filename: find_searchpath.cxx
+// Created by:  drose (09Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "find_searchpath.h"
+
+#include <unistd.h>
+
+string
+find_searchpath(const vector<string> &directories, const string &filename) {
+  vector<string>::const_iterator di;
+
+  for (di = directories.begin(); di != directories.end(); ++di) {
+    string path = (*di) + "/" + filename;
+    if (access(path.c_str(), F_OK) == 0) {
+      return path;
+    }
+  }
+
+  return string();
+}

+ 20 - 0
ppremake/find_searchpath.h

@@ -0,0 +1,20 @@
+// Filename: find_searchpath.h
+// Created by:  drose (09Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef FIND_SEARCHPATH_H
+#define FIND_SEARCHPATH_H
+
+#include "ppremake.h"
+
+#include <vector>
+
+// Searchs for the given filename along the indicated set of
+// directories, and returns the first place in which it is found, or
+// empty string if it is not found.
+string find_searchpath(const vector<string> &directories,
+		       const string &filename);
+
+#endif
+

+ 755 - 0
ppremake/gnu_getopt.c

@@ -0,0 +1,755 @@
+/* Getopt for GNU.
+   NOTE: getopt is now part of the C library, so if you don't know what
+   "Keep this file name-space clean" means, talk to [email protected]
+   before changing it!
+
+   Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94
+   	Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.  */
+
+
+#include <ppremake.h>
+
+#if !defined(HAVE_GETOPT)
+
+#ifdef WIN32_VC
+/* This file seems particularly egregious with this particular warning,
+   but it's not clear why.  Disable. */
+/* C4028: formal parameter N different from declaration */
+#pragma warning (disable : 4028)
+#endif
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+   Ditto for AIX 3.2 and <stdlib.h>.  */
+#ifndef _NO_PROTO
+#define _NO_PROTO
+#endif
+
+#ifndef __STDC__
+/* This is a separate conditional since some stdc systems
+   reject `defined (const)'.  */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+   actually compiling the library itself.  This code is part of the GNU C
+   Library, but also included in many other GNU distributions.  Compiling
+   and linking in this code is a waste when using the GNU C library
+   (especially if it is a shared library).  Rather than having every GNU
+   program understand `configure --with-gnu-libc' and omit the object files,
+   it is simpler to just do this in the source for each such file.  */
+
+#if defined (_LIBC) || !defined (__GNU_LIBRARY__)
+
+
+/* This needs to come after some library #include
+   to get __GNU_LIBRARY__ defined.  */
+#ifdef	__GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+   contain conflicting prototypes for getopt.  */
+#include <stdlib.h>
+#endif	/* GNU C library.  */
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+   but it behaves differently for the user, since it allows the user
+   to intersperse the options with the other arguments.
+
+   As `getopt' works, it permutes the elements of ARGV so that,
+   when it is done, all the options precede everything else.  Thus
+   all application programs are extended to handle flexible argument order.
+
+   Setting the environment variable POSIXLY_CORRECT disables permutation.
+   Then the behavior is completely standard.
+
+   GNU application programs can use a third alternative mode in which
+   they can distinguish the relative order of options and other arguments.  */
+
+#include "gnu_getopt.h"
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns EOF, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+/* XXX 1003.2 says this must be 1 before any call.  */
+int optind = 0;
+
+/* The next char to be scanned in the option-element
+   in which the last option character we returned was found.
+   This allows us to pick up the scan where we left off.
+
+   If this is zero, or a null string, it means resume the scan
+   by advancing to the next ARGV-element.  */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+   for unrecognized options.  */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+   This must be initialized on some systems to avoid linking in the
+   system's own getopt implementation.  */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+   If the caller did not specify anything,
+   the default is REQUIRE_ORDER if the environment variable
+   POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+   REQUIRE_ORDER means don't recognize them as options;
+   stop option processing when the first non-option is seen.
+   This is what Unix does.
+   This mode of operation is selected by either setting the environment
+   variable POSIXLY_CORRECT, or using `+' as the first character
+   of the list of option characters.
+
+   PERMUTE is the default.  We permute the contents of ARGV as we scan,
+   so that eventually all the non-options are at the end.  This allows options
+   to be given in any order, even with programs that were not written to
+   expect this.
+
+   RETURN_IN_ORDER is an option available to programs that were written
+   to expect options and other ARGV-elements in any order and that care about
+   the ordering of the two.  We describe each non-option ARGV-element
+   as if it were the argument of an option with character code 1.
+   Using `-' as the first character of the list of option characters
+   selects this mode of operation.
+
+   The special argument `--' forces an end of option-scanning regardless
+   of the value of `ordering'.  In the case of RETURN_IN_ORDER, only
+   `--' can cause `getopt' to return EOF with `optind' != ARGC.  */
+
+static enum
+{
+  REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable.  */
+static char *posixly_correct;
+
+#ifdef	__GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+   because there are many ways it can cause trouble.
+   On some systems, it contains special magic macros that don't work
+   in GCC.  */
+#include <string.h>
+#define	my_index	strchr
+#else
+
+/* Avoid depending on library functions or files
+   whose names are inconsistent.  */
+
+char *getenv ();
+
+static char *
+my_index (str, chr)
+     const char *str;
+     int chr;
+{
+  while (*str)
+    {
+      if (*str == chr)
+	return (char *) str;
+      str++;
+    }
+  return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+   If not using GCC, it is ok not to declare it.  */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+   That was relevant to code that was here before.  */
+#ifndef __STDC__
+/* gcc with -traditional declares the built-in strlen to return int,
+   and has done so at least since version 2.4.5. -- rms.  */
+extern int strlen (const char *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments.  */
+
+/* Describe the part of ARGV that contains non-options that have
+   been skipped.  `first_nonopt' is the index in ARGV of the first of them;
+   `last_nonopt' is the index after the last of them.  */
+
+static int first_nonopt;
+static int last_nonopt;
+
+/* Exchange two adjacent subsequences of ARGV.
+   One subsequence is elements [first_nonopt,last_nonopt)
+   which contains all the non-options that have been skipped so far.
+   The other is elements [last_nonopt,optind), which contains all
+   the options processed since those non-options were skipped.
+
+   `first_nonopt' and `last_nonopt' are relocated so that they describe
+   the new indices of the non-options in ARGV after they are moved.  */
+
+static void
+exchange (argv)
+     char **argv;
+{
+  int bottom = first_nonopt;
+  int middle = last_nonopt;
+  int top = optind;
+  char *tem;
+
+  /* Exchange the shorter segment with the far end of the longer segment.
+     That puts the shorter segment into the right place.
+     It leaves the longer segment in the right place overall,
+     but it consists of two parts that need to be swapped next.  */
+
+  while (top > middle && middle > bottom)
+    {
+      if (top - middle > middle - bottom)
+	{
+	  /* Bottom segment is the short one.  */
+	  int len = middle - bottom;
+	  register int i;
+
+	  /* Swap it with the top part of the top segment.  */
+	  for (i = 0; i < len; i++)
+	    {
+	      tem = argv[bottom + i];
+	      argv[bottom + i] = argv[top - (middle - bottom) + i];
+	      argv[top - (middle - bottom) + i] = tem;
+	    }
+	  /* Exclude the moved bottom segment from further swapping.  */
+	  top -= len;
+	}
+      else
+	{
+	  /* Top segment is the short one.  */
+	  int len = top - middle;
+	  register int i;
+
+	  /* Swap it with the bottom part of the bottom segment.  */
+	  for (i = 0; i < len; i++)
+	    {
+	      tem = argv[bottom + i];
+	      argv[bottom + i] = argv[middle + i];
+	      argv[middle + i] = tem;
+	    }
+	  /* Exclude the moved top segment from further swapping.  */
+	  bottom += len;
+	}
+    }
+
+  /* Update records for the slots the non-options now occupy.  */
+
+  first_nonopt += (optind - last_nonopt);
+  last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made.  */
+
+static const char *
+_getopt_initialize (optstring)
+     const char *optstring;
+{
+  /* Start processing options with ARGV-element 1 (since ARGV-element 0
+     is the program name); the sequence of previously skipped
+     non-option ARGV-elements is empty.  */
+
+  first_nonopt = last_nonopt = optind = 1;
+
+  nextchar = NULL;
+
+  posixly_correct = getenv ("POSIXLY_CORRECT");
+
+  /* Determine how to handle the ordering of options and nonoptions.  */
+
+  if (optstring[0] == '-')
+    {
+      ordering = RETURN_IN_ORDER;
+      ++optstring;
+    }
+  else if (optstring[0] == '+')
+    {
+      ordering = REQUIRE_ORDER;
+      ++optstring;
+    }
+  else if (posixly_correct != NULL)
+    ordering = REQUIRE_ORDER;
+  else
+    ordering = PERMUTE;
+
+  return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+   given in OPTSTRING.
+
+   If an element of ARGV starts with '-', and is not exactly "-" or "--",
+   then it is an option element.  The characters of this element
+   (aside from the initial '-') are option characters.  If `getopt'
+   is called repeatedly, it returns successively each of the option characters
+   from each of the option elements.
+
+   If `getopt' finds another option character, it returns that character,
+   updating `optind' and `nextchar' so that the next call to `getopt' can
+   resume the scan with the following option character or ARGV-element.
+
+   If there are no more option characters, `getopt' returns `EOF'.
+   Then `optind' is the index in ARGV of the first ARGV-element
+   that is not an option.  (The ARGV-elements have been permuted
+   so that those that are not options now come last.)
+
+   OPTSTRING is a string containing the legitimate option characters.
+   If an option character is seen that is not listed in OPTSTRING,
+   return '?' after printing an error message.  If you set `opterr' to
+   zero, the error message is suppressed but we still return '?'.
+
+   If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+   so the following text in the same ARGV-element, or the text of the following
+   ARGV-element, is returned in `optarg'.  Two colons mean an option that
+   wants an optional arg; if there is text in the current ARGV-element,
+   it is returned in `optarg', otherwise `optarg' is set to zero.
+
+   If OPTSTRING starts with `-' or `+', it requests different methods of
+   handling the non-option ARGV-elements.
+   See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+   Long-named options begin with `--' instead of `-'.
+   Their names may be abbreviated as long as the abbreviation is unique
+   or is an exact match for some defined option.  If they have an
+   argument, it follows the option name in the same ARGV-element, separated
+   from the option name by a `=', or else the in next ARGV-element.
+   When `getopt' finds a long-named option, it returns 0 if that option's
+   `flag' field is nonzero, the value of the option's `val' field
+   if the `flag' field is zero.
+
+   The elements of ARGV aren't really const, because we permute them.
+   But we pretend they're const in the prototype to be compatible
+   with other systems.
+
+   LONGOPTS is a vector of `struct option' terminated by an
+   element containing a name which is zero.
+
+   LONGIND returns the index in LONGOPT of the long-named option found.
+   It is only valid when a long-named option has been found by the most
+   recent call.
+
+   If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+   long-named options.  */
+
+int
+_getopt_internal (argc, argv, optstring, longopts, longind, long_only)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+     const struct option *longopts;
+     int *longind;
+     int long_only;
+{
+  optarg = NULL;
+
+  if (optind == 0)
+    optstring = _getopt_initialize (optstring);
+
+  if (nextchar == NULL || *nextchar == '\0')
+    {
+      /* Advance to the next ARGV-element.  */
+
+      if (ordering == PERMUTE)
+	{
+	  /* If we have just processed some options following some non-options,
+	     exchange them so that the options come first.  */
+
+	  if (first_nonopt != last_nonopt && last_nonopt != optind)
+	    exchange ((char **) argv);
+	  else if (last_nonopt != optind)
+	    first_nonopt = optind;
+
+	  /* Skip any additional non-options
+	     and extend the range of non-options previously skipped.  */
+
+	  while (optind < argc
+		 && (argv[optind][0] != '-' || argv[optind][1] == '\0'))
+	    optind++;
+	  last_nonopt = optind;
+	}
+
+      /* The special ARGV-element `--' means premature end of options.
+	 Skip it like a null option,
+	 then exchange with previous non-options as if it were an option,
+	 then skip everything else like a non-option.  */
+
+      if (optind != argc && !strcmp (argv[optind], "--"))
+	{
+	  optind++;
+
+	  if (first_nonopt != last_nonopt && last_nonopt != optind)
+	    exchange ((char **) argv);
+	  else if (first_nonopt == last_nonopt)
+	    first_nonopt = optind;
+	  last_nonopt = argc;
+
+	  optind = argc;
+	}
+
+      /* If we have done all the ARGV-elements, stop the scan
+	 and back over any non-options that we skipped and permuted.  */
+
+      if (optind == argc)
+	{
+	  /* Set the next-arg-index to point at the non-options
+	     that we previously skipped, so the caller will digest them.  */
+	  if (first_nonopt != last_nonopt)
+	    optind = first_nonopt;
+	  return EOF;
+	}
+
+      /* If we have come to a non-option and did not permute it,
+	 either stop the scan or describe it to the caller and pass it by.  */
+
+      if ((argv[optind][0] != '-' || argv[optind][1] == '\0'))
+	{
+	  if (ordering == REQUIRE_ORDER)
+	    return EOF;
+	  optarg = argv[optind++];
+	  return 1;
+	}
+
+      /* We have found another option-ARGV-element.
+	 Skip the initial punctuation.  */
+
+      nextchar = (argv[optind] + 1
+		  + (longopts != NULL && argv[optind][1] == '-'));
+    }
+
+  /* Decode the current option-ARGV-element.  */
+
+  /* Check whether the ARGV-element is a long option.
+
+     If long_only and the ARGV-element has the form "-f", where f is
+     a valid short option, don't consider it an abbreviated form of
+     a long option that starts with f.  Otherwise there would be no
+     way to give the -f short option.
+
+     On the other hand, if there's a long option "fubar" and
+     the ARGV-element is "-fu", do consider that an abbreviation of
+     the long option, just like "--fu", and not "-f" with arg "u".
+
+     This distinction seems to be the most useful approach.  */
+
+  if (longopts != NULL
+      && (argv[optind][1] == '-'
+	  || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1])))))
+    {
+      char *nameend;
+      const struct option *p;
+      const struct option *pfound = NULL;
+      int exact = 0;
+      int ambig = 0;
+      int indfound;
+      int option_index;
+
+      for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+	/* Do nothing.  */ ;
+
+      /* Test all long options for either exact match
+	 or abbreviated matches.  */
+      for (p = longopts, option_index = 0; p->name; p++, option_index++)
+	if (!strncmp (p->name, nextchar, nameend - nextchar))
+	  {
+	    if (nameend - nextchar == (int) strlen (p->name))
+	      {
+		/* Exact match found.  */
+		pfound = p;
+		indfound = option_index;
+		exact = 1;
+		break;
+	      }
+	    else if (pfound == NULL)
+	      {
+		/* First nonexact match found.  */
+		pfound = p;
+		indfound = option_index;
+	      }
+	    else
+	      /* Second or later nonexact match found.  */
+	      ambig = 1;
+	  }
+
+      if (ambig && !exact)
+	{
+	  if (opterr)
+	    fprintf (stderr, "%s: option `%s' is ambiguous\n",
+		     argv[0], argv[optind]);
+	  nextchar += strlen (nextchar);
+	  optind++;
+	  return '?';
+	}
+
+      if (pfound != NULL)
+	{
+	  option_index = indfound;
+	  optind++;
+	  if (*nameend)
+	    {
+	      /* Don't test has_arg with >, because some C compilers don't
+		 allow it to be used on enums.  */
+	      if (pfound->has_arg)
+		optarg = nameend + 1;
+	      else
+		{
+		  if (opterr)
+		    {
+		      if (argv[optind - 1][1] == '-')
+			/* --option */
+			fprintf (stderr,
+				 "%s: option `--%s' doesn't allow an argument\n",
+				 argv[0], pfound->name);
+		      else
+			/* +option or -option */
+			fprintf (stderr,
+			     "%s: option `%c%s' doesn't allow an argument\n",
+			     argv[0], argv[optind - 1][0], pfound->name);
+		    }
+		  nextchar += strlen (nextchar);
+		  return '?';
+		}
+	    }
+	  else if (pfound->has_arg == 1)
+	    {
+	      if (optind < argc)
+		optarg = argv[optind++];
+	      else
+		{
+		  if (opterr)
+		    fprintf (stderr, "%s: option `%s' requires an argument\n",
+			     argv[0], argv[optind - 1]);
+		  nextchar += strlen (nextchar);
+		  return optstring[0] == ':' ? ':' : '?';
+		}
+	    }
+	  nextchar += strlen (nextchar);
+	  if (longind != NULL)
+	    *longind = option_index;
+	  if (pfound->flag)
+	    {
+	      *(pfound->flag) = pfound->val;
+	      return 0;
+	    }
+	  return pfound->val;
+	}
+
+      /* Can't find it as a long option.  If this is not getopt_long_only,
+	 or the option starts with '--' or is not a valid short
+	 option, then it's an error.
+	 Otherwise interpret it as a short option.  */
+      if (!long_only || argv[optind][1] == '-'
+	  || my_index (optstring, *nextchar) == NULL)
+	{
+	  if (opterr)
+	    {
+	      if (argv[optind][1] == '-')
+		/* --option */
+		fprintf (stderr, "%s: unrecognized option `--%s'\n",
+			 argv[0], nextchar);
+	      else
+		/* +option or -option */
+		fprintf (stderr, "%s: unrecognized option `%c%s'\n",
+			 argv[0], argv[optind][0], nextchar);
+	    }
+	  nextchar = (char *) "";
+	  optind++;
+	  return '?';
+	}
+    }
+
+  /* Look at and handle the next short option-character.  */
+
+  {
+    char c = *nextchar++;
+    char *temp = my_index (optstring, c);
+
+    /* Increment `optind' when we start to process its last character.  */
+    if (*nextchar == '\0')
+      ++optind;
+
+    if (temp == NULL || c == ':')
+      {
+	if (opterr)
+	  {
+	    if (posixly_correct)
+	      /* 1003.2 specifies the format of this message.  */
+	      fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c);
+	    else
+	      fprintf (stderr, "%s: invalid option -- %c\n", argv[0], c);
+	  }
+	optopt = c;
+	return '?';
+      }
+    if (temp[1] == ':')
+      {
+	if (temp[2] == ':')
+	  {
+	    /* This is an option that accepts an argument optionally.  */
+	    if (*nextchar != '\0')
+	      {
+		optarg = nextchar;
+		optind++;
+	      }
+	    else
+	      optarg = NULL;
+	    nextchar = NULL;
+	  }
+	else
+	  {
+	    /* This is an option that requires an argument.  */
+	    if (*nextchar != '\0')
+	      {
+		optarg = nextchar;
+		/* If we end this ARGV-element by taking the rest as an arg,
+		   we must advance to the next element now.  */
+		optind++;
+	      }
+	    else if (optind == argc)
+	      {
+		if (opterr)
+		  {
+		    /* 1003.2 specifies the format of this message.  */
+		    fprintf (stderr, "%s: option requires an argument -- %c\n",
+			     argv[0], c);
+		  }
+		optopt = c;
+		if (optstring[0] == ':')
+		  c = ':';
+		else
+		  c = '?';
+	      }
+	    else
+	      /* We already incremented `optind' once;
+		 increment it again when taking next ARGV-elt as argument.  */
+	      optarg = argv[optind++];
+	    nextchar = NULL;
+	  }
+      }
+    return c;
+  }
+}
+
+int
+getopt (argc, argv, optstring)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+{
+  return _getopt_internal (argc, argv, optstring,
+			   (const struct option *) 0,
+			   (int *) 0,
+			   0);
+}
+
+#endif	/* _LIBC or not __GNU_LIBRARY__.  */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+   the above definition of `getopt'.  */
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+
+      c = getopt (argc, argv, "abc:d:0123456789");
+      if (c == EOF)
+	break;
+
+      switch (c)
+	{
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+	  if (digit_optind != 0 && digit_optind != this_option_optind)
+	    printf ("digits occur in two different argv-elements.\n");
+	  digit_optind = this_option_optind;
+	  printf ("option %c\n", c);
+	  break;
+
+	case 'a':
+	  printf ("option a\n");
+	  break;
+
+	case 'b':
+	  printf ("option b\n");
+	  break;
+
+	case 'c':
+	  printf ("option c with value `%s'\n", optarg);
+	  break;
+
+	case '?':
+	  break;
+
+	default:
+	  printf ("?? getopt returned character code 0%o ??\n", c);
+	}
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+	printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
+
+#endif /* HAVE_GETOPT */

+ 125 - 0
ppremake/gnu_getopt.h

@@ -0,0 +1,125 @@
+/* Declarations for getopt.
+   Copyright (C) 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.  */
+
+#ifndef _GNU_GETOPT_H
+#define _GNU_GETOPT_H 1
+
+/* We don't want to collide with a system getopt() it if it exists.
+   Redefine our symbols accordingly. */
+
+#define getopt gnu_getopt
+#define optind gnu_optind
+#define opterr gnu_opterr
+#define optopt gnu_optopt
+#define optarg gnu_optarg
+#define getopt_long gnu_getopt_long
+#define getopt_long_only gnu_getopt_long_only
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+extern char EXPCL_DTOOL *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns EOF, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+extern int EXPCL_DTOOL optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+   for unrecognized options.  */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized.  */
+
+extern int optopt;
+
+/* Describe the long-named options requested by the application.
+   The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+   of `struct option' terminated by an element containing a name which is
+   zero.
+
+   The field `has_arg' is:
+   no_argument		(or 0) if the option does not take an argument,
+   required_argument	(or 1) if the option requires an argument,
+   optional_argument 	(or 2) if the option takes an optional argument.
+
+   If the field `flag' is not NULL, it points to a variable that is set
+   to the value given in the field `val' when the option is found, but
+   left unchanged if the option is not found.
+
+   To have a long-named option do something other than set an `int' to
+   a compiled-in constant, such as set a value from `optarg', set the
+   option's `flag' field to zero and its `val' field to a nonzero
+   value (the equivalent single-letter option character, if there is
+   one).  For long options that have a zero `flag' field, `getopt'
+   returns the contents of the `val' field.  */
+
+struct option
+{
+#if	__STDC__
+  const char *name;
+#else
+  char *name;
+#endif
+  /* has_arg can't be an enum because some compilers complain about
+     type mismatches in all the code that assumes it is an int.  */
+  int has_arg;
+  int *flag;
+  int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'.  */
+
+#define	no_argument		0
+#define required_argument	1
+#define optional_argument	2
+
+extern EXPCL_DTOOL int
+getopt (int argc, char *const *argv, const char *shortopts);
+extern EXPCL_DTOOL int
+getopt_long (int argc, char *const *argv, const char *shortopts,
+	     const struct option *long_options, int *opt_index);
+extern EXPCL_DTOOL int
+getopt_long_only (int argc, char *const *argv,
+		  const char *shortopts,
+		  const struct option *long_options, 
+		  int *opt_index);
+
+/* Internal only.  Users should not call this directly.  */
+extern int _getopt_internal (int argc, char *const *argv,
+			     const char *shortopts,
+		             const struct option *longopts, int *longind,
+			     int long_only);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _GETOPT_H */

+ 1365 - 0
ppremake/ppCommandFile.cxx

@@ -0,0 +1,1365 @@
+// Filename: ppCommandFile.cxx
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppCommandFile.h"
+#include "ppScope.h"
+#include "ppNamedScopes.h"
+#include "ppSubroutine.h"
+#include "tokenize.h"
+
+#include <ctype.h>
+#include <stdio.h>  // for tempnam()
+#include <unistd.h>
+
+static const string begin_comment(BEGIN_COMMENT);
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::WriteState::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPCommandFile::WriteState::
+WriteState() {
+  _out = &cout;
+  _format = WF_collapse;
+  _last_blank = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::WriteState::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPCommandFile::WriteState::
+WriteState(const WriteState &copy) :
+  _out(copy._out),
+  _format(copy._format),
+  _last_blank(copy._last_blank)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::WriteState::write_line
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::WriteState::
+write_line(const string &line) {
+  switch (_format) {
+  case WF_straight:
+    (*_out) << line << "\n";
+    return true;
+
+  case WF_collapse:
+    return write_collapse_line(line);
+
+  case WF_makefile:
+    return write_makefile_line(line);
+  }
+
+  cerr << "Unsupported write format: " << (int)_format << "\n";
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::WriteState::write_collapse_line
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::WriteState::
+write_collapse_line(const string &line) {
+  if (line.empty()) {
+    if (!_last_blank) {
+      (*_out) << "\n";
+      _last_blank = true;
+    }
+    
+  } else {
+    _last_blank = false;
+    (*_out) << line << "\n";
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::WriteState::write_makefile_line
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::WriteState::
+write_makefile_line(const string &line) {
+  if (line.length() <= 72) {
+    return write_collapse_line(line);
+  }
+  _last_blank = false;
+
+  // In makefile mode, long variable assignment lines are folded after
+  // the assignment.
+  vector<string> words;
+  tokenize_whitespace(line, words);
+
+  if (words.size() > 2 && (words[1] == "=" || words[1] == ":")) {
+    // This appears to be a variable assignment or a dependency rule;
+    // fold it.
+    (*_out) << words[0] << " " << words[1];
+    vector<string>::const_iterator wi;
+    int col = 80;
+    wi = words.begin() + 2;
+    while (wi != words.end()) {
+      col += (*wi).length() + 1;
+      if (col > 72) {
+	(*_out) << " \\\n   ";
+	col = 4 + (*wi).length();
+      }
+      (*_out) << " " << (*wi);
+      ++wi;
+    }
+    (*_out) << "\n";
+
+  } else {
+    // This is not a variable assignment, so just write it out.
+    (*_out) << line << "\n";
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPCommandFile::
+PPCommandFile(PPScope *scope) {
+  _native_scope = scope;
+  _scope = scope;
+  _got_command = false;
+  _in_for = false;
+  _if_nesting = (IfNesting *)NULL;
+  _block_nesting = (BlockNesting *)NULL;
+  _write_state = new WriteState;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPCommandFile::
+~PPCommandFile() {
+  delete _write_state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::set_scope
+//       Access: Public
+//  Description: Changes the command file to use the indicated scope.
+//               This scope will *not* be deleted when the command
+//               file destructs.
+////////////////////////////////////////////////////////////////////
+void PPCommandFile::
+set_scope(PPScope *scope) {
+  _scope = scope;
+  _native_scope = scope;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::get_scope
+//       Access: Public
+//  Description: Returns the current scope associated with the command
+//               file.  This may change as the command file is
+//               processed (e.g. between #begin .. #end sequences),
+//               and it may or may not be tied to the life of the
+//               PPCommandFile itself.
+////////////////////////////////////////////////////////////////////
+PPScope *PPCommandFile::
+get_scope() const {
+  return _scope;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::read_file
+//       Access: Public
+//  Description: Reads input from the given filename.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+read_file(const string &filename) {
+  ifstream in(filename.c_str());
+
+  if (!in) {
+    cerr << "Unable to open " << filename << ".\n";
+    return false;
+  }
+
+  PushFilename pushed(_scope, filename);
+
+  if (!read_stream(in)) {
+    if (!in.eof()) {
+      cerr << "Error reading " << filename << ".\n";
+    }
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::read_stream
+//       Access: Public
+//  Description: Reads input from the given stream.  Each line is
+//               read, commands are processed, variables are expanded,
+//               and the resulting output is sent to write_line()
+//               one line at a time.  The return value is true if the
+//               entire file is read with no errors, false if there is
+//               some problem.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+read_stream(istream &in) {
+  string line;
+  getline(in, line);
+  begin_read();
+  while (!in.fail() && !in.eof()) {
+    if (!read_line(line)) {
+      return false;
+    }
+    getline(in, line);
+  }
+
+  if (!end_read()) {
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::begin_read
+//       Access: Public
+//  Description: Resets to the beginning-of-the-stream state, in
+//               preparation for a sequence of read_line() calls.
+////////////////////////////////////////////////////////////////////
+void PPCommandFile::
+begin_read() {
+  assert(_if_nesting == (IfNesting *)NULL);
+  assert(_block_nesting == (BlockNesting *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::read_line
+//       Access: Public
+//  Description: Reads one line at a time, as if from the input
+//               stream.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+read_line(string line) {
+  // First things first: strip off any comment in the line.
+
+  // We only recognize comments that are proceeded by whitespace, or
+  // that start at the beginning of the line.
+  size_t comment = line.find(begin_comment);
+  while (comment != string::npos && 
+	 !(comment == 0 || isspace(line[comment - 1]))) {
+    comment = line.find(begin_comment, comment + begin_comment.length());
+  }
+
+  if (comment != string::npos) {
+    // Also strip any whitespace leading up to the comment.
+    while (comment > 0 && isspace(line[comment - 1])) {
+      comment--;
+    }
+    line = line.substr(0, comment);
+  }
+
+  // If the comment was at the beginning of the line, ignore the whole
+  // line, including its whitespace.
+  if (comment != 0) {
+    if (_in_for) {
+      // Save up the lines for later execution if we're within a #forscopes.
+      _saved_lines.push_back(line);
+    }
+    
+    if (_got_command) {
+      return handle_command(line);
+      
+    } else {
+      // Find the beginning of the line--skip initial whitespace.
+      size_t p = 0;
+      while (p < line.length() && isspace(line[p])) {
+	p++;
+      }
+      
+      if (p == line.length()) {
+	// The line is empty.  Make it truly empty.
+	line = "";
+	
+      } else {
+	if (p + 1 < line.length() && line[p] == COMMAND_PREFIX && 
+	    isalpha(line[p + 1])) {
+	  // This is a special command.
+	  return handle_command(line.substr(p + 1));
+	}
+      }
+      
+      if (!_in_for && !failed_if()) {
+	return _write_state->write_line(_scope->expand_string(line));
+      }
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::end_read
+//       Access: Public
+//  Description: Finishes up the input stream, after a sequence of
+//               read_line() calls.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+end_read() {
+  bool okflag = true;
+
+  if (_if_nesting != (IfNesting *)NULL) {
+    cerr << "Unclosed if\n";
+    _if_nesting = (IfNesting *)NULL;
+    okflag = false;
+  }
+
+  if (_block_nesting != (BlockNesting *)NULL) {
+    switch (_block_nesting->_state) {
+    case BS_begin:
+      cerr << "Unclosed begin " << _block_nesting->_name << "\n";
+      break;
+
+    case BS_forscopes:
+    case BS_nested_forscopes:
+      cerr << "Unclosed forscopes " << _block_nesting->_name << "\n";
+      break;
+
+    case BS_foreach:
+    case BS_nested_foreach:
+      cerr << "Unclosed foreach " << _block_nesting->_name << "\n";
+      break;
+
+    case BS_defsub:
+      cerr << "Unclosed defsub " << _block_nesting->_name << "\n";
+      break;
+
+    case BS_output:
+      cerr << "Unclosed output " << _block_nesting->_name << "\n";
+      break;
+    }
+    _block_nesting = (BlockNesting *)NULL;
+    okflag = false;
+  }
+
+  return okflag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_command
+//       Access: Protected
+//  Description: Handles a macro command.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_command(const string &line) {
+  if (_got_command) {
+    // If we were still processing a command from last time, keep
+    // going; this line is just a continuation.
+    _params += line;
+
+  } else {
+    // This is the first line of a new command.
+
+    // Pull off the first word and the rest of the line.
+    size_t p = 0;
+    while (p < line.length() && !isspace(line[p])) {
+      p++;
+    }
+    _command = line.substr(0, p);
+  
+    // Skip whitespace between the command and its arguments.
+    while (p < line.length() && isspace(line[p])) {
+      p++;
+    }
+    _params = line.substr(p);
+  }
+
+  if (_params[_params.length() - 1] == '\\') {
+    // If the line ends with a backslash, there's more to come before
+    // we can process the command.
+    _got_command = true;
+    _params[_params.length() - 1] = ' ';
+    return true;
+  }
+
+  // We're completely done scanning the command now.
+  _got_command = false;
+
+  if (_command == "if") {
+    return handle_if_command();
+
+  } else if (_command == "elif") {
+    return handle_elif_command();
+  
+  } else if (_command == "else") {
+    return handle_else_command();
+
+  } else if (_command == "endif") {
+    return handle_endif_command();
+
+  } else if (failed_if()) {
+    // If we're in the middle of a failed #if, we ignore all commands
+    // except for the if-related commands, above.
+    return true;
+
+  } else if (_command == "begin") {
+    return handle_begin_command();
+
+  } else if (_command == "forscopes") {
+    return handle_forscopes_command();
+
+  } else if (_command == "foreach") {
+    return handle_foreach_command();
+
+  } else if (_command == "format") {
+    return handle_format_command();
+
+  } else if (_command == "output") {
+    return handle_output_command();
+
+  } else if (_command == "defsub") {
+    return handle_defsub_command();
+
+  } else if (_command == "end") {
+    return handle_end_command();
+
+  } else if (_in_for) {
+    // If we're saving up #forscopes commands, we ignore any following
+    // commands for now.
+    return true;
+
+  } else if (_command == "include") {
+    return handle_include_command();
+
+  } else if (_command == "sinclude") {
+    return handle_sinclude_command();
+
+  } else if (_command == "call") {
+    return handle_call_command();
+
+  } else if (_command == "error") {
+    return handle_error_command();
+
+  } else if (_command == "defer") {
+    return handle_defer_command();
+
+  } else if (_command == "define") {
+    return handle_define_command();
+
+  } else if (_command == "set") {
+    return handle_set_command();
+
+  } else if (_command == "map") {
+    return handle_map_command();
+  }
+   
+  cerr << "Invalid command: " << COMMAND_PREFIX << _command << "\n";
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_if_command
+//       Access: Protected
+//  Description: Handles the #if command: conditionally evaluate the
+//               following code.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_if_command() {
+  if (failed_if()) {
+    // If we're *already* inside a failed if, we don't have to
+    // evaluate this one, but we do need to record the nesting level.
+
+    IfNesting *nest = new IfNesting;
+    nest->_state = IS_done;
+    nest->_next = _if_nesting;
+    _if_nesting = nest;
+
+  } else {
+
+    // If the parameter string evaluates to empty, the case is false.
+    // Otherwise the case is true.  However, if we're currently
+    // scanning #forscopes or something, we don't evaluate this at
+    // all, because it doesn't matter.
+    if (!_in_for) {
+      _params = _scope->expand_string(_params);
+    }
+    
+    bool is_empty = true;
+    string::const_iterator si;
+    for (si = _params.begin(); si != _params.end() && is_empty; ++si) {
+      is_empty = isspace(*si);
+    }
+    
+    IfNesting *nest = new IfNesting;
+    nest->_state = is_empty ? IS_off : IS_on;
+    nest->_next = _if_nesting;
+    _if_nesting = nest;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_elif_command
+//       Access: Protected
+//  Description: Handles the #elif command: conditionally evaluate
+//               the following code, following a failed #if command.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_elif_command() {
+  if (_if_nesting == (IfNesting *)NULL) {
+    cerr << "elif encountered without if.\n";
+    return false;
+  }
+  if (_if_nesting->_state == IS_else) {
+    cerr << "elif encountered after else.\n";
+    return false;
+  }
+  if (_if_nesting->_state == IS_on || _if_nesting->_state == IS_done) {
+    // If we passed the #if above, we don't need to evaluate the #elif.
+    _if_nesting->_state = IS_done;
+    return true;
+  }
+
+  // If the parameter string evaluates to empty, the case is false.
+  // Otherwise the case is true.
+  if (!_in_for) {
+    _params = _scope->expand_string(_params);
+  }
+
+  bool is_empty = true;
+  string::const_iterator si;
+  for (si = _params.begin(); si != _params.end() && is_empty; ++si) {
+    is_empty = isspace(*si);
+  }
+
+  _if_nesting->_state = is_empty ? IS_off : IS_on;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_else_command
+//       Access: Protected
+//  Description: Handles the #else command: evaluate the following
+//               code following a failed #if command.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_else_command() {
+  if (_if_nesting == (IfNesting *)NULL) {
+    cerr << "else encountered without if.\n";
+    return false;
+  }
+  if (_if_nesting->_state == IS_else) {
+    cerr << "else encountered after else.\n";
+    return false;
+  }
+  if (_if_nesting->_state == IS_on || _if_nesting->_state == IS_done) {
+    _if_nesting->_state = IS_done;
+    return true;
+  }
+
+  _if_nesting->_state = IS_else;
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_endif_command
+//       Access: Protected
+//  Description: Handles the #endif command: close a preceeding #if
+//               command.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_endif_command() {
+  if (_if_nesting == (IfNesting *)NULL) {
+    cerr << "endif encountered without if.\n";
+    return false;
+  }
+
+  IfNesting *nest = _if_nesting;
+  _if_nesting = _if_nesting->_next;
+  delete nest;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_begin_command
+//       Access: Protected
+//  Description: Handles the #begin command: begin a named scope
+//               block.  The variables defined between this command
+//               and the corresponding #end command will be local to
+//               this named scope.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_begin_command() {
+  BlockNesting *nest = new BlockNesting;
+  nest->_state = BS_begin;
+  nest->_name = trim_blanks(_scope->expand_string(_params));
+  nest->_write_state = _write_state;
+  nest->_scope = _scope;
+  nest->_next = _block_nesting;
+
+  if (contains_whitespace(nest->_name)) {
+    cerr << "Attempt to define scope named \"" << nest->_name 
+	 << "\".\nScope names may not contain whitespace.\n";
+    return false;
+  }
+
+  if (nest->_name.find(SCOPE_DIRNAME_SEPARATOR) != string::npos) {
+    cerr << "Attempt to define scope named \"" << nest->_name 
+	 << "\".\nScope names may not contain the '"
+	 << SCOPE_DIRNAME_SEPARATOR << "' character.\n";
+    return false;
+  }
+
+  _block_nesting = nest;
+
+  if (nest->_name == "global") {
+    // There's a special case for the named scope "global": this
+    // refers to the global scope, allowing us to define macros
+    // etc. that all scopes can see.
+    _scope = PPScope::get_bottom_scope();
+
+  } else {
+    PPScope *named_scope = _scope->get_named_scopes()->make_scope(nest->_name);
+    named_scope->set_parent(_scope);
+    _scope = named_scope;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_forscopes_command
+//       Access: Protected
+//  Description: Handles the #forscopes command: interpret all the lines
+//               between this command and the corresponding #end
+//               command once for each occurrence of a named scope
+//               with the given name.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_forscopes_command() {
+  BlockNesting *nest = new BlockNesting;
+  nest->_state = _in_for ? BS_nested_forscopes : BS_forscopes;
+  nest->_name = trim_blanks(_scope->expand_string(_params));
+  nest->_write_state = _write_state;
+  nest->_scope = _scope;
+  nest->_next = _block_nesting;
+
+  _block_nesting = nest;
+
+  if (!_in_for) {
+    _in_for = true;
+    _saved_lines.clear();
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_foreach_command
+//       Access: Protected
+//  Description: Handles the #foreach command: interpret all the lines
+//               between this command and the corresponding #end
+//               command once for each word in the argument.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_foreach_command() {
+  // Get the parameters of the foreach command.  The first word is the
+  // name of the variable to substitute in (and which should appear on
+  // the matching #end command), and the remaining words are the
+  // values to substitute in.
+  vector<string> words;
+  tokenize_whitespace(_scope->expand_string(_params), words);
+
+  if (words.empty()) {
+    cerr << "#foreach requires at least one parameter.\n";
+    return false;
+  }
+
+  string variable_name = words.front();
+
+  BlockNesting *nest = new BlockNesting;
+  nest->_state = _in_for ? BS_nested_foreach : BS_foreach;
+  nest->_name = variable_name;
+  nest->_write_state = _write_state;
+  nest->_scope = _scope;
+  nest->_next = _block_nesting;
+
+  nest->_words.insert(nest->_words.end(), words.begin() + 1, words.end());
+
+  _block_nesting = nest;
+
+  if (!_in_for) {
+    _in_for = true;
+    _saved_lines.clear();
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_format_command
+//       Access: Protected
+//  Description: Handles the #format command: change the formatting
+//               mode of lines as they are output.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_format_command() {
+  _params = trim_blanks(_scope->expand_string(_params));
+  if (_params == "straight") {
+    _write_state->_format = WF_straight;
+
+  } else if (_params == "collapse") {
+    _write_state->_format = WF_collapse;
+
+  } else if (_params == "makefile") {
+    _write_state->_format = WF_makefile;
+
+  } else {
+    cerr << "Ignoring invalid write format: " << _params << "\n";
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_output_command
+//       Access: Protected
+//  Description: Handles the #output command: all text between this
+//               command and the corresponding #end command will be
+//               sent to the indicated output file.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_output_command() {
+  BlockNesting *nest = new BlockNesting;
+  nest->_state = BS_output;
+  nest->_name = trim_blanks(_scope->expand_string(_params));
+  nest->_write_state = _write_state;
+  nest->_scope = _scope;
+  nest->_next = _block_nesting;
+
+  _block_nesting = nest;
+
+  if (!_in_for) {
+    string filename = nest->_name;
+    if (filename.empty()) {
+      cerr << "Attempt to output to empty filename\n";
+      return false;
+    }
+    
+    string prefix = _scope->expand_variable("DIRPREFIX");
+    if (filename[0] != '/') {
+      filename = prefix + filename;
+    }
+    
+    nest->_true_name = filename;
+    nest->_tempnam = (char *)NULL;
+
+    if (access(filename.c_str(), F_OK) == 0) {
+      // If the file already exists, create a temporary file first.
+      
+      nest->_tempnam = tempnam((prefix + ".").c_str(), "pptmp");
+      assert(nest->_tempnam != (char *)NULL);
+      
+      nest->_output.open(nest->_tempnam);
+      if (nest->_output.fail()) {
+	cerr << "Unable to open output file " << nest->_tempnam << "\n";
+	return false;
+      }
+      
+    } else {
+      // If the file does not already exist, create it directly instead
+      // of monkeying around with temporary files.
+      cerr << "Generating " << filename << "\n";
+      
+      nest->_output.open(filename.c_str(), ios::out, 0666);
+      if (nest->_output.fail()) {
+	cerr << "Unable to open output file " << filename << "\n";
+	return false;
+      }
+    }
+    
+    _write_state = new WriteState(*_write_state);
+    _write_state->_out = &nest->_output;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_defsub_command
+//       Access: Protected
+//  Description: Handles the #defsub command: save all the lines
+//               between this command and the matching #end as a
+//               callable subroutine to be invoked by a later #call
+//               command.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_defsub_command() {
+  // The only parameter of #defsub is the name of the subroutine.
+  string subroutine_name = trim_blanks(_scope->expand_string(_params));
+
+  if (subroutine_name.empty()) {
+    cerr << "#defsub requires at least one parameter.\n";
+    return false;
+  }
+
+  if (_in_for) {
+    cerr << "#defsub may not appear within a #forscopes or #foreach block,\n"
+	 << "or within another #defsub block.\n";
+    return false;
+  }
+
+  BlockNesting *nest = new BlockNesting;
+  nest->_state = BS_defsub;
+  nest->_name = subroutine_name;
+  nest->_write_state = _write_state;
+  nest->_scope = _scope;
+  nest->_next = _block_nesting;
+
+  _block_nesting = nest;
+
+  _in_for = true;
+  _saved_lines.clear();
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_end_command
+//       Access: Protected
+//  Description: Handles the #end command.  This closes a number of
+//               different kinds of blocks, like #begin and #forscopes.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_end_command() {
+  if (_block_nesting == (BlockNesting *)NULL) {
+    cerr << "Unmatched end " << _params << ".\n";
+    return false;
+  }
+
+  string name = trim_blanks(_scope->expand_string(_params));
+  if (name != _block_nesting->_name) {
+    cerr << "end " << name << " encountered where end "
+	 << _block_nesting->_name << " expected.\n";
+    return false;
+  }
+
+  BlockNesting *nest = _block_nesting;
+
+  _scope = nest->_scope;
+  if (_write_state != nest->_write_state) {
+    delete _write_state;
+    _write_state = nest->_write_state;
+  }
+
+  _block_nesting = nest->_next;
+
+  if (nest->_state == BS_forscopes) {
+    // Now replay all of the saved lines.
+    _in_for = false;
+    if (!replay_forscopes(nest->_name)) {
+      return false;
+    }
+
+  } else if (nest->_state == BS_foreach) {
+    // Now replay all of the saved lines.
+    _in_for = false;
+    if (!replay_foreach(nest->_name, nest->_words)) {
+      return false;
+    }
+
+  } else if (nest->_state == BS_defsub) {
+    // Save all of the saved lines as a named subroutine.
+    _in_for = false;
+    PPSubroutine *sub = new PPSubroutine;
+    sub->_lines.swap(_saved_lines);
+
+    // Remove the #end command.  This will fail if someone makes an
+    // #end command that spans multiple lines.  Don't do that.
+    assert(!sub->_lines.empty());
+    sub->_lines.pop_back();
+
+    PPSubroutine::define_sub(nest->_name, sub);
+
+  } else if (nest->_state == BS_output) {
+    if (!_in_for) {
+      if (!nest->_output) {
+	cerr << "Error while writing " << nest->_true_name << "\n";
+	return false;
+      }
+      nest->_output.close();
+
+      // Verify the output file.
+      if (nest->_tempnam != (char *)NULL) {
+	if (!compare_output(nest->_tempnam, nest->_true_name)) {
+	  return false;
+	}
+	free(nest->_tempnam);
+      }
+    }
+  }
+
+
+  delete nest;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_include_command
+//       Access: Protected
+//  Description: Handles the #include command: the indicated file is
+//               read and processed at this point.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_include_command() {
+  string filename = trim_blanks(_scope->expand_string(_params));
+
+  // We allow optional quotation marks around the filename.
+  if (filename.length() >= 2 &&
+      filename[0] == '"' && 
+      filename[filename.length() - 1] == '"') {
+    filename = filename.substr(1, filename.length() - 2);
+  }
+
+  return include_file(filename);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_sinclude_command
+//       Access: Protected
+//  Description: Handles the #sinclude command: the indicated file is
+//               read and processed at this point.  This is different
+//               from #include only in that if the file does not
+//               exist, there is no error; instead, nothing happens.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_sinclude_command() {
+  string filename = trim_blanks(_scope->expand_string(_params));
+
+  // We allow optional quotation marks around the filename.
+  if (filename.length() >= 2 &&
+      filename[0] == '"' && 
+      filename[filename.length() - 1] == '"') {
+    filename = filename.substr(1, filename.length() - 2);
+  }
+
+  if (access(filename.c_str(), F_OK) != 0) {
+    // No such file; no error.
+    return true;
+  }
+
+  return include_file(filename);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_call_command
+//       Access: Protected
+//  Description: Handles the #call command: the indicated named
+//               subroutine is read and processed at this point.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_call_command() {
+  string subroutine_name = trim_blanks(_scope->expand_string(_params));
+
+  if (subroutine_name.empty()) {
+    cerr << "#call with no parameter.\n";
+  }
+
+  const PPSubroutine *sub = PPSubroutine::get_sub(subroutine_name);
+  if (sub == (const PPSubroutine *)NULL) {
+    cerr << "Attempt to call undefined subroutine " << subroutine_name << "\n";
+  }
+
+  vector<string>::const_iterator li;
+  for (li = sub->_lines.begin(); li != sub->_lines.end(); ++li) {
+    if (!read_line(*li)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_error_command
+//       Access: Protected
+//  Description: Handles the #error command: terminate immediately
+//               with the given error message.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_error_command() {
+  if (!_params.empty()) {
+    cerr << _params << "\n";
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_defer_command
+//       Access: Protected
+//  Description: Handles the #defer command: define a new variable or
+//               change the definition of an existing variable.  This
+//               is different from #define in that the variable
+//               definition is not immediately expanded; it will be
+//               expanded when the variable is later used.  This
+//               allows the definition of variables that depend on
+//               other variables whose values have not yet been
+//               defined.  This is akin to GNU make's = assignment.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_defer_command() {
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string varname = _params.substr(0, p);
+  
+  // Skip whitespace between the variable name and its definition.
+  while (p < _params.length() && isspace(_params[p])) {
+    p++;
+  }
+  string def = _params.substr(p);
+
+  // We don't expand the variable's definition immediately; it will be
+  // expanded when the variable is referenced later.  However, we
+  // should expand any simple self-reference immediately, to allow for
+  // recursive definitions.
+  def = _scope->expand_self_reference(def, varname);
+
+  _scope->define_variable(varname, def);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_define_command
+//       Access: Protected
+//  Description: Handles the #define command: define a new variable or
+//               change the definition of an existing variable.  The
+//               variable definition is immediately expanded.  This is
+//               akin to GNU make's := assignment.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_define_command() {
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string varname = _params.substr(0, p);
+  
+  // Skip whitespace between the variable name and its definition.
+  while (p < _params.length() && isspace(_params[p])) {
+    p++;
+  }
+  string def = _scope->expand_string(_params.substr(p));
+  _scope->define_variable(varname, def);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_set_command
+//       Access: Protected
+//  Description: Handles the #set command: change the definition of an
+//               existing variable.
+//
+//               This is different from #defer in two ways: (1) the
+//               variable in question must already have been #defined
+//               elsewhere, (2) if the variable was #defined in some
+//               parent scope, this will actually change the variable
+//               in the parent scope, rather than shadowing it in the
+//               local scope.  Like #define and unlike #defer, the
+//               variable definition is expanded immediately, similar
+//               to GNU make's := operator.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_set_command() {
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string varname = _params.substr(0, p);
+  
+  // Skip whitespace between the variable name and its definition.
+  while (p < _params.length() && isspace(_params[p])) {
+    p++;
+  }
+  string def = _scope->expand_string(_params.substr(p));
+
+  if (!_scope->set_variable(varname, def)) {
+    cerr << "Attempt to set undefined variable " << varname << "\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::handle_map_command
+//       Access: Protected
+//  Description: Handles the #map command: define a new map variable.
+//               This is a special kind of variable declaration that
+//               creates a variable that can be used as a function to
+//               look up variable expansions within a number of
+//               different named scopes, accessed by keyword.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+handle_map_command() {
+  // Pull off the first word and the rest of the params.
+  size_t p = 0;
+  while (p < _params.length() && !isspace(_params[p])) {
+    p++;
+  }
+  string varname = _params.substr(0, p);
+  
+  // Skip whitespace between the variable name and its definition.
+  while (p < _params.length() && isspace(_params[p])) {
+    p++;
+  }
+  string def = _params.substr(p);
+
+  // We don't expand the variable's definition immediately; it will be
+  // expanded when the variable is referenced later.  However, we
+  // should expand any simple self-reference immediately, to allow for
+  // recursive definitions.
+  def = _scope->expand_string(def);
+
+  _scope->define_map_variable(varname, def);
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::include_file
+//       Access: Protected
+//  Description: The internal implementation of #include: includes a
+//               particular named file at this point.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+include_file(const string &filename) {
+  ifstream in(filename.c_str());
+
+  if (!in) {
+    cerr << "Unable to open include file " << filename << ".\n";
+    return false;
+  }
+
+  PushFilename pushed(_scope, filename);
+
+  string line;
+  getline(in, line);
+  while (!in.fail() && !in.eof()) {
+    if (!read_line(line)) {
+      return false;
+    }
+    getline(in, line);
+  }
+
+  if (!in.eof()) {
+    cerr << "Error reading " << filename << ".\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::replay_forscopes
+//       Access: Protected
+//  Description: Replays all the lines that were saved during a
+//               previous #forscopes..#end block.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+replay_forscopes(const string &name) {
+  bool okflag = true;
+
+  vector<string> lines;
+  lines.swap(_saved_lines);
+
+  // Remove the #end command.  This will fail if someone makes an #end
+  // command that spans multiple lines.  Don't do that.
+  assert(!lines.empty());
+  lines.pop_back();
+
+  PPNamedScopes *named_scopes = _scope->get_named_scopes();
+
+  // Extract out the scope names from the #forscopes .. #end name.  This
+  // is a space-delimited list of scope names.
+  vector<string> words;
+  tokenize_whitespace(name, words);
+
+  // Now traverse the named scopes with these names.
+  vector<string>::const_iterator wi;
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    PPNamedScopes::Scopes scopes;
+    named_scopes->get_scopes(*wi, scopes);
+    
+    PPNamedScopes::Scopes::const_iterator si;
+    for (si = scopes.begin(); si != scopes.end(); ++si) {
+      PPScope::push_scope(_scope);
+      _scope = (*si);
+      
+      vector<string>::const_iterator li;
+      for (li = lines.begin(); li != lines.end() && okflag; ++li) {
+	okflag = read_line(*li);
+      }
+      _scope = PPScope::pop_scope();
+    }
+  }
+
+  return okflag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::replay_foreach
+//       Access: Protected
+//  Description: Replays all the lines that were saved during a
+//               previous #foreach..#end block.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+replay_foreach(const string &varname, const vector<string> &words) {
+  bool okflag = true;
+
+  vector<string> lines;
+  lines.swap(_saved_lines);
+
+  // Remove the #end command.  This will fail if someone makes an #end
+  // command that spans multiple lines.  Don't do that.
+  assert(!lines.empty());
+  lines.pop_back();
+
+  // Now traverse through the saved words.
+  vector<string>::const_iterator wi;
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    _scope->define_variable(varname, (*wi));
+    vector<string>::const_iterator li;
+    for (li = lines.begin(); li != lines.end() && okflag; ++li) {
+      okflag = read_line(*li);
+    }
+  }
+
+  return okflag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::compare_output
+//       Access: Protected
+//  Description: After a temporary file has been written due to an
+//               #output command, compare the results to the original
+//               file.  If they are different, remove the original
+//               file and rename the temporary file; if they are the
+//               same, remove the temporary file.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+compare_output(const string &temp_name, const string &true_name) {
+  ifstream in_a(temp_name.c_str());
+  ifstream in_b(true_name.c_str());
+
+  int a = in_a.get();
+  int b = in_b.get(); 
+  bool differ = (a != b);
+  while (!in_a.eof() && !in_b.eof() && !differ) {
+    a = in_a.get();
+    b = in_b.get(); 
+    differ = (a != b);
+  }
+
+  in_a.close();
+  in_b.close();
+
+  if (differ) {
+    cerr << "Generating " << true_name << "\n";
+
+    if (unlink(true_name.c_str()) < 0) {
+      cerr << "Unable to remove old " << true_name << "\n";
+      return false;
+    }
+
+    if (rename(temp_name.c_str(), true_name.c_str()) < 0) {
+      cerr << "Unable to rename temporary file " << temp_name
+	   << " to " << true_name << "\n";
+      return false;
+    }
+
+  } else {
+    //    cerr << "File " << true_name << " is unchanged.\n";
+    if (unlink(temp_name.c_str()) < 0) {
+      cerr << "Warning: unable to remove temporary file " << temp_name << "\n";
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::failed_if
+//       Access: Protected
+//  Description: Returns true if we are currently within a failed #if
+//               block (or in an #else block for a passed #if block),
+//               or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PPCommandFile::
+failed_if() const {
+  return (_if_nesting != (IfNesting *)NULL && 
+	  (_if_nesting->_state == IS_off || _if_nesting->_state == IS_done));
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::PushFilename::Constructor
+//       Access: Public
+//  Description: This special class changes the current filename of
+//               the PPCommandFile.  The idea is to create one of
+//               these when the filename is changed (for instance, to
+//               read in a new file via an #include directive); when
+//               the variable then goes out of scope, it will restore
+//               the previous filename.
+//
+//               This updates the scope with the appropriate
+//               variables.
+////////////////////////////////////////////////////////////////////
+PPCommandFile::PushFilename::
+PushFilename(PPScope *scope, const string &filename) {
+  _scope = scope;
+  _old_thisdirprefix = _scope->get_variable("THISDIRPREFIX");
+  _old_thisfilename = _scope->get_variable("THISFILENAME");
+
+  _scope->define_variable("THISFILENAME", filename);
+  size_t slash = filename.rfind('/');
+  if (slash == string::npos) {
+    _scope->define_variable("THISDIRPREFIX", string());
+  } else {
+    _scope->define_variable("THISDIRPREFIX", filename.substr(0, slash + 1));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPCommandFile::PushFilename::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPCommandFile::PushFilename::
+~PushFilename() {
+  _scope->define_variable("THISDIRPREFIX", _old_thisdirprefix);
+  _scope->define_variable("THISFILENAME", _old_thisfilename);
+}

+ 153 - 0
ppremake/ppCommandFile.h

@@ -0,0 +1,153 @@
+// Filename: ppCommandFile.h
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPCOMMANDFILE_H
+#define PPCOMMANDFILE_H
+
+#include "ppremake.h"
+
+#include <map>
+#include <vector>
+
+class PPScope;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPCommandFile
+// Description : This encapsulates a file that contains #commands to
+//               execute (like #define, #if, #begin .. #end),
+//               $[variables] to expand, and plain text to output.
+////////////////////////////////////////////////////////////////////
+class PPCommandFile {
+public:
+  PPCommandFile(PPScope *scope);
+  ~PPCommandFile();
+
+  void set_scope(PPScope *scope);
+  PPScope *get_scope() const;
+
+  bool read_file(const string &filename);
+  bool read_stream(istream &in);
+  void begin_read();
+  bool read_line(string line);
+  bool end_read();
+
+protected:
+  bool handle_command(const string &line);
+  bool handle_if_command();
+  bool handle_elif_command();
+  bool handle_else_command();
+  bool handle_endif_command();
+
+  bool handle_begin_command();
+  bool handle_forscopes_command();
+  bool handle_foreach_command();
+  bool handle_format_command();
+  bool handle_output_command();
+  bool handle_defsub_command();
+  bool handle_end_command();
+
+  bool handle_include_command();
+  bool handle_sinclude_command();
+  bool handle_call_command();
+  bool handle_error_command();
+
+  bool handle_defer_command();
+  bool handle_define_command();
+  bool handle_set_command();
+  bool handle_map_command();
+
+  bool include_file(const string &filename);
+  bool replay_forscopes(const string &name);
+  bool replay_foreach(const string &varname, const vector<string> &words);
+  bool compare_output(const string &temp_name, const string &true_name);
+  bool failed_if() const;
+
+private:
+  class PushFilename {
+  public:
+    PushFilename(PPScope *scope, const string &filename);
+    ~PushFilename();
+
+    PPScope *_scope;
+    string _old_thisdirprefix;
+    string _old_thisfilename;
+  };
+
+private:
+  PPScope *_native_scope;
+  PPScope *_scope;
+
+  enum IfState {
+    IS_on,   // e.g. a passed #if
+    IS_else, // after matching an #else
+    IS_off,  // e.g. a failed #if
+    IS_done  // e.g. after reaching an #else or #elif for a passed #if.
+  };
+
+  class IfNesting {
+  public:
+    IfState _state;
+    IfNesting *_next;
+  };
+
+  enum BlockState {
+    BS_begin,
+    BS_forscopes,
+    BS_nested_forscopes,
+    BS_foreach,
+    BS_nested_foreach,
+    BS_defsub,
+    BS_output
+  };
+
+  enum WriteFormat {
+    WF_straight,   // write lines directly as they come in
+    WF_collapse,   // collapse out consecutive blank lines
+    WF_makefile    // fancy makefile formatting
+  };
+
+  class WriteState {
+  public:
+    WriteState();
+    WriteState(const WriteState &copy);
+    bool write_line(const string &line);
+    bool write_collapse_line(const string &line);
+    bool write_makefile_line(const string &line);
+
+    ostream *_out;
+    WriteFormat _format;
+    bool _last_blank;
+  };
+  
+  class BlockNesting {
+  public:
+    BlockState _state;
+    string _name;
+    WriteState *_write_state;
+    PPScope *_scope;
+    string _true_name;
+    char *_tempnam;
+    ofstream _output;
+    vector<string> _words;
+    BlockNesting *_next;
+  };
+
+  bool _got_command;
+  bool _in_for;
+  IfNesting *_if_nesting;
+  BlockNesting *_block_nesting;
+  string _command;
+  string _params;
+  WriteState *_write_state;
+
+  vector<string> _saved_lines;
+
+  friend PPCommandFile::IfNesting;
+  friend PPCommandFile::WriteState;
+  friend PPCommandFile::BlockNesting;
+};
+
+
+#endif

+ 550 - 0
ppremake/ppDirectoryTree.cxx

@@ -0,0 +1,550 @@
+// Filename: ppDirectoryTree.cxx
+// Created by:  drose (28Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppDirectoryTree.h"
+#include "ppScope.h"
+#include "ppNamedScopes.h"
+#include "ppCommandFile.h"
+#include "tokenize.h"
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <algorithm>
+
+PPDirectoryTree *current_output_directory = (PPDirectoryTree *)NULL;
+
+// An STL object to sort directories in order by dependency and then
+// by name, used in get_child_dirnames().
+class SortDirectoriesByDependencyAndName {
+public:
+  bool operator () (const PPDirectoryTree *a, const PPDirectoryTree *b) const {
+    if (a->get_depends_index() != b->get_depends_index()) {
+      return a->get_depends_index() < b->get_depends_index();
+    }
+    return a->get_dirname() < b->get_dirname();
+  }
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::Constructor
+//       Access: Public
+//  Description: Creates the root level of the PPDirectoryTree.
+////////////////////////////////////////////////////////////////////
+PPDirectoryTree::
+PPDirectoryTree() {
+  _scope = (PPScope *)NULL;
+  _source = (PPCommandFile *)NULL;
+  _parent = (PPDirectoryTree *)NULL;
+  _depth = 0;
+  _depends_index = 0;
+  _computing_depends_index = false;
+  _dirnames = new Dirnames;
+
+  _dirname = "top";
+  (*_dirnames).insert(Dirnames::value_type(_dirname, this));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::Destructor
+//       Access: Public
+//  Description: When a tree root destructs, all of its children are
+//               also destroyed.
+////////////////////////////////////////////////////////////////////
+PPDirectoryTree::
+~PPDirectoryTree() {
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    delete (*ci);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::Constructor
+//       Access: Protected
+//  Description: Creates a new directory level that automatically adds
+//               itself to its parent's children list.
+////////////////////////////////////////////////////////////////////
+PPDirectoryTree::
+PPDirectoryTree(const string &dirname, PPDirectoryTree *parent) :
+  _dirname(dirname),
+  _parent(parent)
+{
+  assert(_parent != (PPDirectoryTree *)NULL);
+  _scope = (PPScope *)NULL;
+  _source = (PPCommandFile *)NULL;
+  _parent->_children.push_back(this);
+  _depth = _parent->_depth + 1;
+  _depends_index = 0;
+  _computing_depends_index = false;
+  _dirnames = _parent->_dirnames;
+
+  bool inserted = 
+    (*_dirnames).insert(Dirnames::value_type(_dirname, this)).second;
+  if (!inserted) {
+    cerr << "Warning: multiple directories encountered named "
+	 << _dirname << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::scan
+//       Access: Public
+//  Description: Reads in the complete hierarchy of source files.
+//               prefix is the pathname to the directory on disk,
+//               ending in slash.
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+scan(const string &prefix, PPNamedScopes *named_scopes) {
+  if (!r_scan(prefix)) {
+    return false;
+  }
+
+  if (!read_source_file(prefix, named_scopes)) {
+    return false;
+  }
+
+  if (!read_depends_file(named_scopes)) {
+    return false;
+  }
+
+  if (!resolve_dependencies()) {
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::count_source_files
+//       Access: Public
+//  Description: Returns the number of directories within the tree
+//               that actually have a Sources.pp file that was read.
+////////////////////////////////////////////////////////////////////
+int PPDirectoryTree::
+count_source_files() const {
+  int count = 0;
+  if (_source != (PPCommandFile *)NULL) {
+    count++;
+  }
+
+  Children::const_iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    count += (*ci)->count_source_files();
+  }
+
+  return count;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_dirname
+//       Access: Public
+//  Description: Returns the name of this particular directory level.
+////////////////////////////////////////////////////////////////////
+const string &PPDirectoryTree::
+get_dirname() const {
+  return _dirname;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_depends_index
+//       Access: Public
+//  Description: Returns the dependency index associated with this
+//               directory.  It is generally true that if directory A
+//               depends on B, then A.get_depends_index() >
+//               B.get_depends_index().
+////////////////////////////////////////////////////////////////////
+int PPDirectoryTree::
+get_depends_index() const {
+  return _depends_index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_path
+//       Access: Public
+//  Description: Returns the relative path from the root to this
+//               particular directory.  This does not include the root
+//               name itself, and does not include a trailing slash.
+////////////////////////////////////////////////////////////////////
+string PPDirectoryTree::
+get_path() const {
+  if (_parent == (PPDirectoryTree *)NULL) {
+    return ".";
+  }
+  if (_parent->_parent == (PPDirectoryTree *)NULL) {
+    return _dirname;
+  }
+  return _parent->get_path() + "/" + _dirname;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_rel_to
+//       Access: Public
+//  Description: Returns the relative path to the other directory from
+//               this one.  This does not include a trailing slash.
+////////////////////////////////////////////////////////////////////
+string PPDirectoryTree::
+get_rel_to(const PPDirectoryTree *other) const {
+  const PPDirectoryTree *a = this;
+  const PPDirectoryTree *b = other;
+
+  if (a == b) {
+    return ".";
+  }
+
+  string prefix, postfix;
+  while (a->_depth > b->_depth) {
+    prefix += "../";
+    a = a->_parent;
+    assert(a != (PPDirectoryTree *)NULL);
+  }
+
+  while (b->_depth > a->_depth) {
+    postfix = b->_dirname + "/" + postfix;
+    b = b->_parent;
+    assert(b != (PPDirectoryTree *)NULL);
+  }
+
+  while (a != b) {
+    prefix += "../";
+    postfix = b->_dirname + "/" + postfix;
+    a = a->_parent;
+    b = b->_parent;
+    assert(a != (PPDirectoryTree *)NULL);
+    assert(b != (PPDirectoryTree *)NULL);
+  }
+
+  string result = prefix + postfix;
+  assert(!result.empty());
+  return result.substr(0, result.length() - 1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::find_dirname
+//       Access: Public
+//  Description: Searches for the first subdirectory found with the
+//               matching dirname.  This is just the name of the
+//               directory itself, not the relative path to the
+//               directory.
+////////////////////////////////////////////////////////////////////
+PPDirectoryTree *PPDirectoryTree::
+find_dirname(const string &dirname) {
+  assert(_dirnames != (Dirnames *)NULL);
+  Dirnames::const_iterator di;
+  di = _dirnames->find(dirname);
+  if (di != _dirnames->end()) {
+    return (*di).second;
+  }
+
+  // No such dirname; too bad.
+  return (PPDirectoryTree *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_source
+//       Access: Public
+//  Description: Returns the source file associated with this level
+//               of the directory hierarchy.  This *might* be NULL.
+////////////////////////////////////////////////////////////////////
+PPCommandFile *PPDirectoryTree::
+get_source() const {
+  return _source;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_num_children
+//       Access: Public
+//  Description: Returns the number of subdirectories below this
+//               level.
+////////////////////////////////////////////////////////////////////
+int PPDirectoryTree::
+get_num_children() const {
+  return _children.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_child
+//       Access: Public
+//  Description: Returns the nth subdirectory below this level.
+////////////////////////////////////////////////////////////////////
+PPDirectoryTree *PPDirectoryTree::
+get_child(int n) const {
+  assert(n >= 0 && n < (int)_children.size());
+  return _children[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_child_dirnames
+//       Access: Public
+//  Description: Returns a single string listing the names of all the
+//               subdirectories of this level, delimited by spaces.
+//
+//               The list is sorted in dependency order such that a
+//               directory is listed after the other directories it
+//               might depend on.
+////////////////////////////////////////////////////////////////////
+string PPDirectoryTree::
+get_child_dirnames() const {
+  Children copy_children = _children;
+  sort(copy_children.begin(), copy_children.end(),
+       SortDirectoriesByDependencyAndName());
+
+  vector<string> words;
+  Children::const_iterator ci;
+  for (ci = copy_children.begin(); ci != copy_children.end(); ++ci) {
+    words.push_back((*ci)->get_dirname());
+  }
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::get_complete_subtree
+//       Access: Public
+//  Description: Returns a single string listing the relative path
+//               from the source root to each source directory at this
+//               level and below, delimited by spaces.
+////////////////////////////////////////////////////////////////////
+string PPDirectoryTree::
+get_complete_subtree() const {
+  Children copy_children = _children;
+  sort(copy_children.begin(), copy_children.end(),
+       SortDirectoriesByDependencyAndName());
+
+  vector<string> words;
+  words.push_back(get_path());
+
+  Children::const_iterator ci;
+  for (ci = copy_children.begin(); ci != copy_children.end(); ++ci) {
+    words.push_back((*ci)->get_complete_subtree());
+  }
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::r_scan
+//       Access: Private
+//  Description: The recursive implementation of scan().
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+r_scan(const string &prefix) {
+  string root_name = ".";
+  if (!prefix.empty()) {
+    root_name = prefix.substr(0, prefix.length() - 1);
+  }
+  
+  DIR *root = opendir(root_name.c_str());
+  if (root == (DIR *)NULL) {
+    cerr << "Unable to scan directory " << root_name << "\n";
+    return false;
+  }
+
+  struct dirent *d;
+  d = readdir(root);
+  while (d != (struct dirent *)NULL) {
+    string dirname = d->d_name;
+
+    if (!dirname.empty() && dirname[0] != '.') {
+      // Do we have a source file in this subdirectory (if it is a
+      // subdirectory)?
+      string next_prefix = prefix + dirname + "/";
+      string source_filename = next_prefix + SOURCE_FILENAME;
+      if (access(source_filename.c_str(), F_OK) == 0) {
+	PPDirectoryTree *subtree = new PPDirectoryTree(dirname, this);
+
+	if (!subtree->r_scan(next_prefix)) {
+	  return false;
+	}
+      }
+    }
+
+    d = readdir(root);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::read_source_file
+//       Access: Private
+//  Description: Recursively reads in the source file at each level,
+//               if defined.
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+read_source_file(const string &prefix, PPNamedScopes *named_scopes) {
+  string source_filename = prefix + SOURCE_FILENAME;
+
+  ifstream in(source_filename.c_str());
+  if (in) {
+    //    cerr << "Reading " << source_filename << "\n";
+
+    named_scopes->set_current(_dirname);
+    _scope = named_scopes->make_scope("");
+    
+    _scope->define_variable("SOURCEFILE", SOURCE_FILENAME);
+    _scope->define_variable("DIRNAME", _dirname);
+    _scope->define_variable("DIRPREFIX", prefix);
+    _scope->define_variable("PATH", get_path());
+    _scope->define_variable("SUBDIRS", get_child_dirnames());
+    _scope->define_variable("SUBTREE", get_complete_subtree());
+    _scope->set_directory(this);
+    
+    _source = new PPCommandFile(_scope);
+    
+    if (!_source->read_stream(in)) {
+      cerr << "Error when reading " << source_filename << "\n";
+      return false;
+    }
+  }
+    
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    if (!(*ci)->read_source_file(prefix + (*ci)->get_dirname() + "/",
+				  named_scopes)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::read_depends_file
+//       Access: Private
+//  Description: Recursively reads in the dependency definition file
+//               for each source file.
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+read_depends_file(PPNamedScopes *named_scopes) {
+  if (_scope != (PPScope *)NULL) {
+    // Read the depends file, so we can determine the relationship
+    // between this source file and all of the other source files.
+
+    string depends_filename = _scope->expand_variable("DEPENDS_FILE");
+    if (depends_filename.empty()) {
+      cerr << "No definition given for $[DEPENDS_FILE], cannot process.\n";
+      return false;
+    }
+    
+    named_scopes->set_current(_dirname);
+    PPCommandFile depends(_scope);
+    if (!depends.read_file(depends_filename)) {
+      cerr << "Error reading dependency definition file "
+	   << depends_filename << ".\n";
+      return false;
+    }
+    
+    // This should have defined the variable DEPENDS, which lists the
+    // various dirnames this source file depends on.
+
+    vector<string> dirnames;
+    tokenize_whitespace(_scope->expand_variable("DEPENDS"), dirnames);
+
+    vector<string>::const_iterator ni;
+    for (ni = dirnames.begin(); ni != dirnames.end(); ++ni) {
+      const string &dirname = (*ni);
+      PPDirectoryTree *dir = find_dirname(dirname);
+      if (dir == (PPDirectoryTree *)NULL) {
+	cerr << "Could not find dependent dirname " << dirname << "\n";
+      } else {
+	if (dir != this) {
+	  _i_depend_on.insert(dir);
+	  dir->_depends_on_me.insert(this);
+	}
+      }
+    }
+  }
+    
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    if (!(*ci)->read_depends_file(named_scopes)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::resolve_dependencies
+//       Access: Private
+//  Description: Visits each directory and assigns a correct
+//               _depends_index to each one, such that if directory A
+//               depends on directory B then A._depends_index >
+//               B._depends_index.
+//
+//               This also detects cycles in the directory dependency
+//               graph.
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+resolve_dependencies() {
+  if (!compute_depends_index()) {
+    return false;
+  }
+
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    if (!(*ci)->resolve_dependencies()) {
+      return false;
+    }
+  }
+
+  // Now that we've resolved all of our children's dependencies,
+  // redefine our SUBDIRS and SUBTREE variables to put things in the
+  // right order.
+  if (_scope != (PPScope *)NULL) {
+    _scope->define_variable("SUBDIRS", get_child_dirnames());
+    _scope->define_variable("SUBTREE", get_complete_subtree());
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPDirectoryTree::compute_depends_index
+//       Access: Private
+//  Description: Computes the dependency score for a particular
+//               directory.  See resolve_dependencies().
+////////////////////////////////////////////////////////////////////
+bool PPDirectoryTree::
+compute_depends_index() {
+  if (_depends_index != 0) {
+    return true;
+  }
+
+  if (_i_depend_on.empty()) {
+    _depends_index = 1;
+    return true;
+  }
+
+  _computing_depends_index = true;
+  int max_index = 0;
+
+  Depends::iterator di;
+  for (di = _i_depend_on.begin(); di != _i_depend_on.end(); ++di) {
+    if ((*di)->_computing_depends_index) {
+      // Oops, we have a cycle!
+      cerr << "Cycle detected in inter-directory dependencies!\n"
+	   << _dirname << " depends on " << (*di)->_dirname << "\n";
+      return false;
+    }
+      
+    if (!(*di)->compute_depends_index()) {
+      // Keep reporting the cycle as we unroll the recursion.
+      cerr << _dirname << " depends on " << (*di)->_dirname << "\n";
+      return false;
+    }
+
+    max_index = max(max_index, (*di)->_depends_index);
+  }
+
+  _computing_depends_index = false;
+  _depends_index = max_index + 1;
+
+  return true;
+}

+ 83 - 0
ppremake/ppDirectoryTree.h

@@ -0,0 +1,83 @@
+// Filename: ppDirectoryTree.h
+// Created by:  drose (28Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPDIRECTORYTREE_H
+#define PPDIRECTORYTREE_H
+
+#include "ppremake.h"
+
+#include <vector>
+#include <map>
+#include <set>
+
+class PPCommandFile;
+class PPScope;
+class PPNamedScopes;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPDirectoryTree
+// Description : Stores the directory hierarchy relationship of the
+//               source tree.  Each PPDirectoryTree object is
+//               one-to-one associated with a PPCommandFile object,
+//               that corresponds to the source file found within this
+//               directory.
+////////////////////////////////////////////////////////////////////
+class PPDirectoryTree {
+public:
+  PPDirectoryTree();
+  ~PPDirectoryTree();
+
+protected:
+  PPDirectoryTree(const string &dirname, PPDirectoryTree *parent);
+
+public:
+  bool scan(const string &prefix, PPNamedScopes *named_scopes);
+
+  int count_source_files() const;
+
+  const string &get_dirname() const;
+  int get_depends_index() const;
+  string get_path() const;
+  string get_rel_to(const PPDirectoryTree *other) const;
+
+  PPDirectoryTree *find_dirname(const string &dirname);
+  PPCommandFile *get_source() const;
+
+  int get_num_children() const;
+  PPDirectoryTree *get_child(int n) const;
+
+  string get_child_dirnames() const;
+  string get_complete_subtree() const;
+
+private:
+  bool r_scan(const string &prefix);
+  bool read_source_file(const string &prefix, PPNamedScopes *named_scopes);
+  bool read_depends_file(PPNamedScopes *named_scopes);
+  bool resolve_dependencies();
+  bool compute_depends_index();
+
+  string _dirname;
+  PPScope *_scope;
+  PPCommandFile *_source;
+  PPDirectoryTree *_parent;
+  typedef vector<PPDirectoryTree *> Children;
+  Children _children;
+  int _depth;
+
+  typedef set<PPDirectoryTree *> Depends;
+  Depends _i_depend_on;
+  Depends _depends_on_me;
+  int _depends_index;
+  bool _computing_depends_index;
+
+
+  typedef map<string, PPDirectoryTree *> Dirnames;
+  Dirnames *_dirnames;
+};
+
+extern PPDirectoryTree *current_output_directory;
+
+#endif
+

+ 176 - 0
ppremake/ppFilenamePattern.cxx

@@ -0,0 +1,176 @@
+// Filename: ppFilenamePattern.cxx
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppFilenamePattern.h"
+
+#include <assert.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPFilenamePattern::
+PPFilenamePattern(const string &pattern) {
+  size_t pct = pattern.find(PATTERN_WILDCARD);
+  _has_wildcard = (pct != string::npos);
+  
+  if (_has_wildcard) {
+    _prefix = pattern.substr(0, pct);
+    _suffix = pattern.substr(pct + 1);
+  } else {
+    _prefix = pattern;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPFilenamePattern::
+PPFilenamePattern(const PPFilenamePattern &copy) :
+  _has_wildcard(copy._has_wildcard),
+  _prefix(copy._prefix),
+  _suffix(copy._suffix)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PPFilenamePattern::
+operator = (const PPFilenamePattern &copy) {
+  _has_wildcard = copy._has_wildcard;
+  _prefix = copy._prefix;
+  _suffix = copy._suffix;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::has_wildcard
+//       Access: Public
+//  Description: Returns true if the filename pattern contained a
+//               wildcard (and hence represents a pattern and not
+//               just a single particular filename), or false if it
+//               did not.
+////////////////////////////////////////////////////////////////////
+bool PPFilenamePattern::
+has_wildcard() const {
+  return _has_wildcard;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::get_pattern
+//       Access: Public
+//  Description: Returns the filename pattern.
+////////////////////////////////////////////////////////////////////
+string PPFilenamePattern::
+get_pattern() const {
+  if (_has_wildcard) {
+    return _prefix + PATTERN_WILDCARD + _suffix;
+  } else {
+    return _prefix;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::get_prefix
+//       Access: Public
+//  Description: Returns the part of the filename pattern before the
+//               wildcard.  If the filename did not contain a
+//               wildcard, this returns the entire filename.
+////////////////////////////////////////////////////////////////////
+const string &PPFilenamePattern::
+get_prefix() const {
+  return _prefix;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::get_suffix
+//       Access: Public
+//  Description: Returns the part of the filename pattern after the
+//               wildcard.  If the filename did not contain a
+//               wildcard, this returns the empty string.
+////////////////////////////////////////////////////////////////////
+const string &PPFilenamePattern::
+get_suffix() const {
+  return _suffix;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::matches_filename
+//       Access: Public
+//  Description: Returns true if the given filename matches the
+//               pattern, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PPFilenamePattern::
+matches(const string &filename) const {
+  if (_has_wildcard) {
+    return 
+      (filename.length() >= _prefix.length() + _suffix.length()) &&
+      (filename.substr(0, _prefix.length()) == _prefix) &&
+      (filename.substr(filename.length() - _suffix.length()) == _suffix);
+
+  } else {
+    return (filename == _prefix);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::extract_body
+//       Access: Public
+//  Description: If the filename matches the pattern
+//               (e.g. matches_filename() returns true), return the
+//               portion of the filename that corresponds to the
+//               wildcard in the pattern: the part of the filename
+//               between the prefix and the suffix.  If the filename
+//               does not match the pattern, or the pattern does not
+//               contain a wildcard, returns empty string.
+////////////////////////////////////////////////////////////////////
+string PPFilenamePattern::
+extract_body(const string &filename) const {
+  if (_has_wildcard) {
+    size_t outside_length = _prefix.length() + _suffix.length();
+    if ((filename.length() >= outside_length) &&
+	(filename.substr(0, _prefix.length()) == _prefix) &&
+	(filename.substr(filename.length() - _suffix.length()) == _suffix)) {
+      return filename.substr(_prefix.length(), filename.length() - outside_length);
+    }
+  }
+
+  return string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPFilenamePattern::transform_filename
+//       Access: Public
+//  Description: Transforms a filename by replacing the parts of the
+//               filename described in the pattern transform_from with
+//               the corresponding parts of the filename described in
+//               this pattern.  If the filename does not match
+//               transform_from, returns the untransformed filename.
+//
+//               It is an error to call this unless both this pattern
+//               and transform_from include a wildcard.
+////////////////////////////////////////////////////////////////////
+string PPFilenamePattern::
+transform(const string &filename,
+	  const PPFilenamePattern &transform_from) const {
+  assert(transform_from._has_wildcard);
+
+  if (transform_from.matches(filename)) {
+    if (!_has_wildcard) {
+      return _prefix;
+    } else {
+      string body = transform_from.extract_body(filename);
+      return _prefix + body + _suffix;
+    }
+  }
+
+  return filename;
+}
+

+ 45 - 0
ppremake/ppFilenamePattern.h

@@ -0,0 +1,45 @@
+// Filename: ppFilenamePattern.h
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPFILENAMEPATTERN_H
+#define PPFILENAMEPATTERN_H
+
+#include "ppremake.h"
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPFilenamePattern
+// Description : This is a string that represents a filename, or a
+//               family of filenames, using the make convention that a
+//               wildcard sign (PATTERN_WILDCARD, typically '%') in
+//               the filename represents any sequence of characters.
+////////////////////////////////////////////////////////////////////
+class PPFilenamePattern {
+public:
+  PPFilenamePattern(const string &pattern);
+  PPFilenamePattern(const PPFilenamePattern &copy);
+  void operator = (const PPFilenamePattern &copy);
+
+  bool has_wildcard() const;
+  string get_pattern() const;
+  const string &get_prefix() const;
+  const string &get_suffix() const;
+
+  bool matches(const string &filename) const;
+  string extract_body(const string &filename) const;
+  string transform(const string &filename,
+		   const PPFilenamePattern &transform_from) const;
+
+private:
+  bool _has_wildcard;
+  string _prefix;
+  string _suffix;
+};
+
+inline ostream &
+operator << (ostream &out, const PPFilenamePattern &pattern) {
+  return out << pattern.get_pattern();
+}
+
+#endif

+ 193 - 0
ppremake/ppMain.cxx

@@ -0,0 +1,193 @@
+// Filename: ppMain.cxx
+// Created by:  drose (28Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppMain.h"
+#include "ppScope.h"
+#include "ppCommandFile.h"
+
+#include <unistd.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPMain::
+PPMain(PPScope *global_scope) {
+  _global_scope = global_scope;
+  PPScope::push_scope(_global_scope);
+
+  _def_scope = (PPScope *)NULL;
+  _defs = (PPCommandFile *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPMain::
+~PPMain() {
+  if (_def_scope != (PPScope *)NULL) {
+    delete _def_scope;
+  }
+  if (_defs != (PPCommandFile *)NULL) {
+    delete _defs;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::read_source
+//       Access: Public
+//  Description: Reads the directory hierarchy of Sources.pp files, at
+//               the indicated directory and below.
+////////////////////////////////////////////////////////////////////
+bool PPMain::
+read_source(const string &root) {
+  // First, read the package file.  We find this either in this
+  // directory, or in some directory above us.
+  string search = root + "/";
+  if (root == ".") {
+    search = "";
+  }
+
+  string package_file = search + PACKAGE_FILENAME;
+
+  while (access(package_file.c_str(), F_OK) != 0) {
+    // We continue to walk up directories as long as we see a source
+    // file in each directory.  When we stop seeing source files, we
+    // stop walking upstairs.
+    string source_file = search + SOURCE_FILENAME;
+    if (access(source_file.c_str(), F_OK) != 0) {
+      cerr << "Could not find ppremake package file " << PACKAGE_FILENAME
+	   << ".\n\n"
+	   << "This file should be present in the top of the source directory tree;\n"
+	   << "it defines implementation-specific variables to control the output\n"
+	   << "of ppremake, as well as pointing out the installed location of\n"
+	   << "important ppremake config files.\n\n";
+      return false;
+    }
+    search += "../";
+    package_file = search + PACKAGE_FILENAME;
+  }
+
+  _def_scope = new PPScope(&_named_scopes);
+  _def_scope->define_variable("PACKAGEFILE", package_file);
+  _def_scope->define_variable("TOPDIRPREFIX", search);
+  _defs = new PPCommandFile(_def_scope);
+
+  //  cerr << "Reading " << package_file << "\n";
+  if (!_defs->read_file(package_file)) {
+    cerr << "Error reading package file " << package_file << ".\n";
+    return false;
+  }
+
+  PPScope::push_scope(_def_scope);
+
+  if (!_tree.scan(search, &_named_scopes)) {
+    return false;
+  }
+
+  _def_scope->define_variable("TREE", _tree.get_complete_subtree());
+
+  if (_tree.count_source_files() == 0) {
+    cerr << "Could not find any source definition files named " << SOURCE_FILENAME
+	 << ".\n\n"
+	 << "A file by this name should be present in each directory of the source\n"
+	 << "hierarchy; it defines the source files and targets that should be\n"
+	 << "built in each directory, as well as the relationships between the\n"
+	 << "directories.\n\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::process_all
+//       Access: Public
+//  Description: Does all the processing on all known directories.
+//               See process().
+////////////////////////////////////////////////////////////////////
+bool PPMain::
+process_all() {
+  return r_process_all(&_tree);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::process
+//       Access: Public
+//  Description: Does the processing associated with the source file
+//               in the indicated subdirectory name.  This involves
+//               reading in the template file and generating whatever
+//               output the template file indicates.
+////////////////////////////////////////////////////////////////////
+bool PPMain::
+process(const string &dirname) {
+  PPDirectoryTree *dir = _tree.find_dirname(dirname);
+  if (dir == (PPDirectoryTree *)NULL) {
+    cerr << "Unknown directory: " << dirname << "\n";
+    return false;
+  }
+
+  if (dir->get_source() == (PPCommandFile *)NULL) {
+    cerr << "No source file in " << dirname << "\n";
+    return false;
+  }
+
+  return p_process(dir);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::r_process_all
+//       Access: Private
+//  Description: The recursive implementation of process_all().
+////////////////////////////////////////////////////////////////////
+bool PPMain::
+r_process_all(PPDirectoryTree *dir) {
+  if (dir->get_source() != (PPCommandFile *)NULL) {
+    if (!p_process(dir)) {
+      return false;
+    }
+  }
+
+  int num_children = dir->get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    if (!r_process_all(dir->get_child(i))) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPMain::p_process
+//       Access: Private
+//  Description: The private implementation of process().
+////////////////////////////////////////////////////////////////////
+bool PPMain::
+p_process(PPDirectoryTree *dir) {
+  current_output_directory = dir;
+  _named_scopes.set_current(dir->get_dirname());
+  PPCommandFile *source = dir->get_source();
+  assert(source != (PPCommandFile *)NULL);
+
+  PPScope *scope = source->get_scope();
+
+  string template_filename = scope->expand_variable("TEMPLATE_FILE");
+  if (template_filename.empty()) {
+    cerr << "No definition given for $[TEMPLATE_FILE], cannot process.\n";
+    return false;
+  }
+
+  PPCommandFile template_file(scope);
+  if (!template_file.read_file(template_filename)) {
+    cerr << "Error reading template file " << template_filename << ".\n";
+    return false;
+  }
+
+  return true;
+}

+ 47 - 0
ppremake/ppMain.h

@@ -0,0 +1,47 @@
+// Filename: ppMain.h
+// Created by:  drose (28Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPMAIN_H
+#define PPMAIN_H
+
+#include "ppremake.h"
+#include "ppDirectoryTree.h"
+#include "ppNamedScopes.h"
+
+class PPScope;
+class PPCommandFile;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPMain
+// Description : Handles the toplevel processing in this program:
+//               holds the tree of source files, and all the scopes,
+//               etc.  Generally get the ball rolling.
+////////////////////////////////////////////////////////////////////
+class PPMain {
+public:
+  PPMain(PPScope *global_scope);
+  ~PPMain();
+
+  bool read_source(const string &root);
+
+  bool process_all();
+  bool process(const string &dirname);
+
+private:
+  bool r_process_all(PPDirectoryTree *dir);
+  bool p_process(PPDirectoryTree *dir);
+
+
+  PPScope *_global_scope;
+  PPScope *_def_scope;
+  PPCommandFile *_defs;
+
+  PPDirectoryTree _tree;
+  PPNamedScopes _named_scopes;
+  PPScope *_parent_scope;
+};
+
+#endif
+

+ 112 - 0
ppremake/ppNamedScopes.cxx

@@ -0,0 +1,112 @@
+// Filename: ppNamedScopes.cxx
+// Created by:  drose (27Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppNamedScopes.h"
+#include "ppScope.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPNamedScopes::
+PPNamedScopes() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPNamedScopes::
+~PPNamedScopes() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::make_scope
+//       Access: Public
+//  Description: Creates a new scope in the current directory name
+//               with the indicated scope name.
+////////////////////////////////////////////////////////////////////
+PPScope *PPNamedScopes::
+make_scope(const string &name) {
+  PPScope *scope = new PPScope(this);
+  _directories[_current][name].push_back(scope);
+  return scope;
+}
+ 
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::get_scopes
+//       Access: Public
+//  Description: Returns a list of all the named scopes matching the
+//               given scope name.  The scope name may be of the form
+//               "dirname/scopename", in which case the dirname may be
+//               another directory name, or "." or "*".  If the
+//               dirname is ".", it is the same as the current
+//               directory name; if it is "*", it represents all
+//               directory names.  If omitted, the current directory
+//               name is implied.
+//
+//               It is the responsibility of the user to ensure that
+//               scopes is empty before calling this function; this
+//               will append to the existing vector without first
+//               clearing it.
+////////////////////////////////////////////////////////////////////
+void PPNamedScopes::
+get_scopes(const string &name, Scopes &scopes) const {
+  string dirname = _current;
+  string scopename = name;
+
+  size_t slash = name.find(SCOPE_DIRNAME_SEPARATOR);
+  if (slash != string::npos) {
+    dirname = name.substr(0, slash);
+    scopename = name.substr(slash + 1);
+    if (dirname == SCOPE_DIRNAME_CURRENT) {
+      dirname = _current;
+    }
+  }
+
+  Directories::const_iterator di;
+
+  if (dirname == SCOPE_DIRNAME_WILDCARD) {
+    for (di = _directories.begin(); di != _directories.end(); ++di) {
+      p_get_scopes((*di).second, scopename, scopes);
+    }
+
+  } else {
+    di = _directories.find(dirname);
+    if (di != _directories.end()) {
+      p_get_scopes((*di).second, scopename, scopes);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::set_current
+//       Access: Public
+//  Description: Changes the currently-active directory, i.e. the
+//               dirname represented by ".".
+////////////////////////////////////////////////////////////////////
+void PPNamedScopes::
+set_current(const string &dirname) {
+  _current = dirname;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPNamedScopes::p_get_scopes
+//       Access: Private
+//  Description: Adds the scopes from the given directory with the
+//               matching name into the returned vector.
+////////////////////////////////////////////////////////////////////
+void PPNamedScopes::
+p_get_scopes(const PPNamedScopes::Named &named, const string &name,
+	     Scopes &scopes) const {
+  Named::const_iterator ni;
+  ni = named.find(name);
+  if (ni != named.end()) {
+    const Scopes &s = (*ni).second;
+    scopes.insert(scopes.end(), s.begin(), s.end());
+  }
+}

+ 47 - 0
ppremake/ppNamedScopes.h

@@ -0,0 +1,47 @@
+// Filename: ppNamedScopes.h
+// Created by:  drose (27Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPNAMEDSCOPES_H
+#define PPNAMEDSCOPES_H
+
+#include "ppremake.h"
+
+#include <map>
+#include <vector>
+
+class PPScope;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPNamedScopes
+// Description : A collection of named scopes, as defined by #begin
+//               .. #end sequences within a series of command files,
+//               each associated with the directory name of the
+//               command file in which it was read.
+////////////////////////////////////////////////////////////////////
+class PPNamedScopes {
+public:
+  PPNamedScopes();
+  ~PPNamedScopes();
+
+  typedef vector<PPScope *> Scopes;
+
+  PPScope *make_scope(const string &name);
+  void get_scopes(const string &name, Scopes &scopes) const;
+
+  void set_current(const string &dirname);
+
+private:  
+  typedef map<string, Scopes> Named;
+
+  void p_get_scopes(const Named &named, const string &name,
+		    Scopes &scopes) const;
+
+  typedef map<string, Named> Directories;
+  Directories _directories;
+  string _current;
+};
+
+#endif
+

+ 1805 - 0
ppremake/ppScope.cxx

@@ -0,0 +1,1805 @@
+// Filename: ppScope.cxx
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppScope.h"
+#include "ppNamedScopes.h"
+#include "ppFilenamePattern.h"
+#include "ppDirectoryTree.h"
+#include "tokenize.h"
+#include "find_searchpath.h"
+
+#include <stdlib.h>
+#include <algorithm>
+#include <ctype.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>  // for perror().
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+static const string variable_patsubst(VARIABLE_PATSUBST);
+
+PPScope::MapVariableDefinition PPScope::_null_map_def;
+
+PPScope::ScopeStack PPScope::_scope_stack;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PPScope::
+PPScope(PPNamedScopes *named_scopes) : 
+  _named_scopes(named_scopes)
+{
+  _directory = (PPDirectoryTree *)NULL;
+  _parent_scope = (PPScope *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::get_named_scopes
+//       Access: Public
+//  Description: Returns a pointer to the PPNamedScopes collection
+//               associated with this scope.  This pointer could be
+//               NULL.
+////////////////////////////////////////////////////////////////////
+PPNamedScopes *PPScope::
+get_named_scopes() const {
+  return _named_scopes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::set_parent
+//       Access: Public
+//  Description: Sets a static parent scope to this scope.  When a
+//               variable reference is undefined in this scope, it
+//               will search first up the static parent chain before
+//               it searches the dynamic scope stack.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+set_parent(PPScope *parent) {
+  _parent_scope = parent;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::get_parent
+//       Access: Public
+//  Description: Returns the static parent scope to this scope, if
+//               any, or NULL if the static parent has not been set.
+//               See set_parent().
+////////////////////////////////////////////////////////////////////
+PPScope *PPScope::
+get_parent() const {
+  return _parent_scope;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::define_variable
+//       Access: Public
+//  Description: Makes a new variable definition.  If the variable
+//               does not already exist in this scope, a new variable
+//               is created, possibly shadowing a variable declaration
+//               in some parent scope.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+define_variable(const string &varname, const string &definition) {
+  _variables[varname] = definition;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::set_variable
+//       Access: Public
+//  Description: Changes the definition of an already-existing
+//               variable.  The variable is changed in whichever scope
+//               it is defined.  Returns false if the variable has not
+//               been defined.
+////////////////////////////////////////////////////////////////////
+bool PPScope::
+set_variable(const string &varname, const string &definition) {
+  if (p_set_variable(varname, definition)) {
+    return true;
+  }
+
+  // Check the scopes on the stack for the variable definition.
+  ScopeStack::reverse_iterator si;
+  for (si = _scope_stack.rbegin(); si != _scope_stack.rend(); ++si) {
+    if ((*si)->p_set_variable(varname, definition)) {
+      return true;
+    }
+  }
+
+  // If the variable isn't defined, we check the environment.
+  const char *env = getenv(varname.c_str());
+  if (env != (const char *)NULL) {
+    // It is defined in the environment; thus, it is implicitly
+    // defined here at the global scope: the bottom of the stack.
+    PPScope *bottom = this;
+    if (!_scope_stack.empty()) {
+      bottom = _scope_stack.front();
+    }
+    bottom->define_variable(varname, definition);
+    return true;
+  }
+
+  // The variable isn't defined anywhere.  Too bad.
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::define_map_variable
+//       Access: Public
+//  Description: Makes a new map variable definition.  This defines a
+//               new variable that can be used as a function to
+//               retrieve variables from within a named scope, based
+//               on a particular key variable.
+//
+//               In this variant of define_map_variable(), the
+//               definition is a string of the form
+//               key_varname(scope_names).
+////////////////////////////////////////////////////////////////////
+void PPScope::
+define_map_variable(const string &varname, const string &definition) {
+  size_t p = definition.find(VARIABLE_OPEN_NESTED);
+  if (p != string::npos && definition[definition.length() - 1] == VARIABLE_CLOSE_NESTED) {
+    size_t q = definition.length() - 1;
+    string scope_names = definition.substr(p + 1, q - (p + 1));
+    string key_varname = definition.substr(0, p);
+    define_map_variable(varname, key_varname, scope_names);
+  } else {
+    // No scoping; not really a map variable.
+    define_map_variable(varname, definition, "");
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::define_map_variable
+//       Access: Public
+//  Description: Makes a new map variable definition.  This defines a
+//               new variable that can be used as a function to
+//               retrieve variables from within a named scope, based
+//               on a particular key variable.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+define_map_variable(const string &varname, const string &key_varname,
+		    const string &scope_names) {
+  MapVariableDefinition &def = _map_variables[varname];
+  def.clear();
+  define_variable(varname, "");
+
+  if (_named_scopes == (PPNamedScopes *)NULL) {
+    return;
+  }
+
+  vector<string> names;
+  tokenize_whitespace(scope_names, names);
+
+  // Get all of the named scopes.
+  PPNamedScopes::Scopes scopes;
+  
+  vector<string>::const_iterator ni;
+  for (ni = names.begin(); ni != names.end(); ++ni) {
+    const string &name = (*ni);
+    _named_scopes->get_scopes(name, scopes);
+  }
+
+  if (scopes.empty()) {
+    return;
+  }
+
+  // Now go through the scopes and build up the results.
+  vector<string> results;
+
+  PPNamedScopes::Scopes::const_iterator si;
+  for (si = scopes.begin(); si != scopes.end(); ++si) {
+    PPScope *scope = (*si);
+    string key_string = scope->expand_variable(key_varname);
+    vector<string> keys;
+    tokenize_whitespace(key_string, keys);
+
+    if (!keys.empty()) {
+      vector<string>::const_iterator ki;
+      results.insert(results.end(), keys.begin(), keys.end());
+      for (ki = keys.begin(); ki != keys.end(); ++ki) {
+	def[*ki] = scope;
+      }
+    }
+  }
+
+  // Also define a traditional variable along with the map variable.
+  define_variable(varname, repaste(results, " "));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::get_variable
+//       Access: Public
+//  Description: Returns the variable definition associated with the
+//               indicated variable name.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+get_variable(const string &varname) const {
+  string result;
+  if (p_get_variable(varname, result)) {
+    return result;
+  }
+
+  // Check the scopes on the stack for the variable definition.
+  ScopeStack::reverse_iterator si;
+  for (si = _scope_stack.rbegin(); si != _scope_stack.rend(); ++si) {
+    if ((*si)->p_get_variable(varname, result)) {
+      return result;
+    }
+  }
+
+  // If the variable isn't defined, we check the environment.
+  const char *env = getenv(varname.c_str());
+  if (env != (const char *)NULL) {
+    return env;
+  }
+
+  // It's not defined anywhere, so it's implicitly empty.
+  return string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_variable
+//       Access: Public
+//  Description: Similar to get_variable(), except the variable
+//               definition is in turn expanded.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_variable(const string &varname) const {
+  return expand_string(get_variable(varname));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::get_directory
+//       Access: Public
+//  Description: Returns the directory level associated with this
+//               scope, if any, or with the nearest parent to this
+//               scope.
+////////////////////////////////////////////////////////////////////
+PPDirectoryTree *PPScope::
+get_directory() const {
+  if (_directory != (PPDirectoryTree *)NULL) {
+    return _directory;
+  }
+
+  // Check the stack.
+  ScopeStack::reverse_iterator si;
+  for (si = _scope_stack.rbegin(); si != _scope_stack.rend(); ++si) {
+    if ((*si)->_directory != (PPDirectoryTree *)NULL) {
+      return (*si)->_directory;
+    }
+  }
+
+  return (PPDirectoryTree *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::set_directory
+//       Access: Public
+//  Description: Associates this scope with the indicated directory
+//               level.  Typically this is done when definition a
+//               scope for a particular source file which exists at a
+//               known directory level.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+set_directory(PPDirectoryTree *directory) {
+  _directory = directory;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_string
+//       Access: Public
+//  Description: Expands out all the variable references in the given
+//               string.  Variables are expanded recursively; that is,
+//               if a variable expansion includes a reference to
+//               another variable name, the second variable name is
+//               expanded.  However, cyclical references are not
+//               expanded.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_string(const string &str) const {
+  return r_expand_string(str, (ExpandedVariable *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_self_reference
+//       Access: Public
+//  Description: Similar to expand_string(), except that only simple
+//               references to the named variable are expanded--other
+//               variable references are left unchanged.  This allows
+//               us to define a variable in terms of its previous
+//               definition.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_self_reference(const string &str, const string &varname) const {
+  // Look for a simple reference to the named variable.  A more
+  // complex reference, like a computed variable name or something
+  // equally loopy, won't work with this simple test.  Too bad.
+  string reference;
+  reference += VARIABLE_PREFIX;
+  reference += VARIABLE_OPEN_BRACE;
+  reference += varname;
+  reference += VARIABLE_CLOSE_BRACE;
+
+  string result;
+
+  size_t p = 0;
+  size_t q = str.find(reference, p);
+  while (q != string::npos) {
+    result += str.substr(p, q - p);
+    p = q;
+    result += r_expand_variable(str, p, (ExpandedVariable *)NULL);
+    q = str.find(reference, p);  
+  }
+
+  result += str.substr(p);
+  return result;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::push_scope
+//       Access: Public, Static
+//  Description: Pushes the indicated scope onto the top of the stack.
+//               When a variable reference is unresolved in the
+//               current scope, the scope stack is searched, in LIFO
+//               order.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+push_scope(PPScope *scope) {
+  _scope_stack.push_back(scope);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::pop_scope
+//       Access: Public, Static
+//  Description: Pops another level off the top of the stack.  See
+//               push_scope().
+////////////////////////////////////////////////////////////////////
+PPScope *PPScope::
+pop_scope() {
+  assert(!_scope_stack.empty());
+  PPScope *back = _scope_stack.back();
+  _scope_stack.pop_back();
+  return back;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::get_bottom_scope
+//       Access: Public, Static
+//  Description: Returns the scope on the bottom of the stack.  This
+//               was the very first scope ever pushed, e.g. the global
+//               scope.
+////////////////////////////////////////////////////////////////////
+PPScope *PPScope::
+get_bottom_scope() {
+  assert(!_scope_stack.empty());
+  return _scope_stack.front();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::p_set_variable
+//       Access: Private
+//  Description: The private implementation of p_set_variable.
+//               Returns true if the variable's definition is found
+//               and set, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PPScope::
+p_set_variable(const string &varname, const string &definition) {
+  Variables::iterator vi;
+  vi = _variables.find(varname);
+  if (vi != _variables.end()) {
+    (*vi).second = definition;
+    return true;
+  }
+
+  if (_parent_scope != (PPScope *)NULL) {
+    return _parent_scope->p_set_variable(varname, definition);
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::p_get_variable
+//       Access: Private
+//  Description: The private implementation of get_variable().  This
+//               checks the local scope only; it does not check the
+//               stack.  It returns true if the variable is defined,
+//               false otherwise..
+////////////////////////////////////////////////////////////////////
+bool PPScope::
+p_get_variable(const string &varname, string &result) const {
+  Variables::const_iterator vi;
+  vi = _variables.find(varname);
+  if (vi != _variables.end()) {
+    result = (*vi).second;
+    return true;
+  }
+
+  if (varname == "RELDIR" && 
+      _directory != (PPDirectoryTree *)NULL &&
+      current_output_directory != (PPDirectoryTree *)NULL) {
+    // $[RELDIR] is a special variable name that evaluates to the
+    // relative directory of the current scope to the current output
+    // directory.
+    result = current_output_directory->get_rel_to(_directory);
+    return true;
+  }
+
+  if (_parent_scope != (PPScope *)NULL) {
+    return _parent_scope->p_get_variable(varname, result);
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::tokenize_params
+//       Access: Private
+//  Description: Separates a string into tokens based on comma
+//               delimiters, e.g. for parameters to a function.
+//               Nested variable references are skipped correctly,
+//               even if they include commas.  Leading and trailing
+//               whitespace in each token is automatically stripped.
+//
+//               If expand is true, the nested variables are
+//               automatically expanded as the string is tokenized;
+//               otherwise, they are left unexpanded.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+tokenize_params(const string &str, vector<string> &tokens,
+		bool expand) const {
+  size_t p = 0;
+  while (p < str.length()) {
+    // Skip initial whitespace.
+    while (p < str.length() && isspace(str[p])) {
+      p++;
+    }
+
+    string token;
+    while (p < str.length() && str[p] != FUNCTION_PARAMETER_SEPARATOR) {
+      if (p + 1 < str.length() && str[p] == VARIABLE_PREFIX &&
+	  str[p + 1] == VARIABLE_OPEN_BRACE) {
+	// Skip a nested variable reference.
+	if (expand) {
+	  token += r_expand_variable(str, p, (ExpandedVariable *)NULL);
+	} else {
+	  token += r_scan_variable(str, p);
+	}
+      } else {
+	token += str[p];
+	p++;
+      }
+    }
+
+    // Back up past trailing whitespace.
+    size_t q = token.length();
+    while (q > 0 && isspace(token[q - 1])) {
+      q--;
+    }
+
+    tokens.push_back(token.substr(0, q));
+    p++;
+
+    if (p == str.length()) {
+      // In this case, we have just read past a trailing comma symbol
+      // at the end of the string, so we have one more empty token.
+      tokens.push_back(string());
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::r_expand_string
+//       Access: Private
+//  Description: The recursive implementation of expand_string().
+//               This function detects cycles in the variable
+//               expansion by storing the set of variable names that
+//               have thus far been expanded in the linked list.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+r_expand_string(const string &str, PPScope::ExpandedVariable *expanded) const {
+  string result;
+
+  // Search for a variable reference.
+  size_t p = 0;
+  while (p < str.length()) {
+    if (p + 1 < str.length() && str[p] == VARIABLE_PREFIX &&
+	str[p + 1] == VARIABLE_OPEN_BRACE) {
+      // Here's a nested variable!  Expand it fully.
+      result += r_expand_variable(str, p, expanded);
+
+    } else {
+      result += str[p];
+      p++;
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::r_scan_variable
+//       Access: Private
+//  Description: Scans past a single variable reference without
+//               expanding it.  On input, str is a string containing a
+//               variable reference (among other stuff), and vp is the
+//               position within the string of the prefix character at
+//               the beginning of the variable reference.
+//
+//               On output, vp is set to the position within the
+//               string of the first character after the variable
+//               reference's closing bracket.  The variable reference
+//               itself is returned.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+r_scan_variable(const string &str, size_t &vp) const {
+
+  // Search for the end of the variable name: an unmatched square
+  // bracket.
+  size_t start = vp;
+  size_t p = vp + 2;
+  while (p < str.length() && str[p] != VARIABLE_CLOSE_BRACE) {
+    if (p + 1 < str.length() && str[p] == VARIABLE_PREFIX && 
+	str[p + 1] == VARIABLE_OPEN_BRACE) {
+      // Here's a nested variable!  Scan past it, matching braces
+      // properly.
+      r_scan_variable(str, p);
+    } else {
+      p++;
+    }
+  }
+
+  if (p < str.length()) {
+    assert(str[p] == VARIABLE_CLOSE_BRACE);
+    p++;
+  } else {
+    cerr << "Warning!  Unclosed variable reference:\n"
+	 << str.substr(vp) << "\n";
+  }
+
+  vp = p;
+  return str.substr(start, vp - start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::r_expand_variable
+//       Access: Private
+//  Description: Expands a single variable reference.  On input, str
+//               is a string containing a variable reference (among
+//               other stuff), and vp is the position within the
+//               string of the prefix character at the beginning of
+//               the variable reference.
+//
+//               On output, vp is set to the position within the
+//               string of the first character after the variable
+//               reference's closing bracket, and the string expansion
+//               of the variable reference is returned.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+r_expand_variable(const string &str, size_t &vp,
+		  PPScope::ExpandedVariable *expanded) const {
+  string varname;
+
+  size_t whitespace_at = 0;
+  size_t open_nested_at = 0;
+
+  // Search for the end of the variable name: an unmatched square
+  // bracket.
+  size_t p = vp + 2;
+  while (p < str.length() && str[p] != VARIABLE_CLOSE_BRACE) {
+    if (p + 1 < str.length() && str[p] == VARIABLE_PREFIX && 
+	str[p + 1] == VARIABLE_OPEN_BRACE) {
+      if (whitespace_at != 0) {
+	// Once we have encountered whitespace, we don't expand
+	// variables inline anymore.  These are now function
+	// parameters, and might need to be expanded in some other
+	// scope.
+	varname += r_scan_variable(str, p);
+      } else {
+	varname += r_expand_variable(str, p, expanded);
+      }
+
+    } else {
+      if (open_nested_at == 0 && str[p] == VARIABLE_OPEN_NESTED) {
+	open_nested_at = p - (vp + 2);
+      }
+      if (open_nested_at == 0 && whitespace_at == 0 && isspace(str[p])) {
+	whitespace_at = p - (vp + 2);
+      }
+      varname += str[p];
+      p++;
+    }
+  }
+
+  if (p < str.length()) {
+    assert(str[p] == VARIABLE_CLOSE_BRACE);
+    p++;
+  } else {
+    cerr << "Warning!  Unclosed variable reference:\n"
+	 << str.substr(vp) << "\n";
+  }
+
+  vp = p;
+
+  // Check for a function expansion.
+  if (whitespace_at != 0) {
+    string funcname = varname.substr(0, whitespace_at);
+    p = whitespace_at;
+    while (p < varname.length() && isspace(varname[p])) {
+      p++;
+    }
+    string params = varname.substr(p);
+
+    // Is it a built-in function?
+
+    if (funcname == "wildcard") {
+      return expand_wildcard(params);
+    } else if (funcname == "isdir") {
+      return expand_isdir(params);
+    } else if (funcname == "libtest") {
+      return expand_libtest(params);
+    } else if (funcname == "bintest") {
+      return expand_bintest(params);
+    } else if (funcname == "shell") {
+      return expand_shell(params);
+    } else if (funcname == "firstword") {
+      return expand_firstword(params);
+    } else if (funcname == "patsubst") {
+      return expand_patsubst(params);
+    } else if (funcname == "subst") {
+      return expand_subst(params);
+    } else if (funcname == "filter") {
+      return expand_filter(params);
+    } else if (funcname == "filter_out" || funcname == "filter-out") {
+      return expand_filter_out(params);
+    } else if (funcname == "sort") {
+      return expand_sort(params);
+    } else if (funcname == "unique") {
+      return expand_unique(params);
+    } else if (funcname == "if") {
+      return expand_if(params);
+    } else if (funcname == "eq") {
+      return expand_eq(params);
+    } else if (funcname == "ne") {
+      return expand_ne(params);
+    } else if (funcname == "not") {
+      return expand_not(params);
+    } else if (funcname == "or") {
+      return expand_or(params);
+    } else if (funcname == "and") {
+      return expand_and(params);
+    } else if (funcname == "upcase") {
+      return expand_upcase(params);
+    } else if (funcname == "downcase") {
+      return expand_downcase(params);
+    } else if (funcname == "closure") {
+      return expand_closure(params);
+    }
+
+    // It must be a map variable.
+    return expand_map_variable(funcname, params);
+  }
+
+  // Now we have the variable name; was it previously expanded?
+  ExpandedVariable *ev;
+  for (ev = expanded; ev != (ExpandedVariable *)NULL; ev = ev->_next) {
+    if (ev->_varname == varname) {
+      // Yes, this is a cyclical expansion.
+      cerr << "Ignoring cyclical expansion of " << varname << "\n";
+      return string();
+    }
+  }
+
+  // And now expand the variable.
+  string expansion;
+
+  // Check for a special inline patsubst operation, like GNU make:
+  // $[varname:%.c=%.o]
+  string patsubst;
+  bool got_patsubst = false;
+  p = varname.find(variable_patsubst);
+  if (p != string::npos) {
+    got_patsubst = true;
+    patsubst = varname.substr(p + variable_patsubst.length());
+    varname = varname.substr(0, p);
+  }
+
+  // Check for special scoping operators in the variable name.
+  p = varname.find(VARIABLE_OPEN_NESTED);
+  if (p != string::npos && varname[varname.length() - 1] == VARIABLE_CLOSE_NESTED) {
+    size_t q = varname.length() - 1;
+    string scope_names = varname.substr(p + 1, q - (p + 1));
+    varname = varname.substr(0, p);
+    expansion = expand_variable_nested(varname, scope_names);
+
+  } else {
+    // No special scoping; just expand the variable name.
+    expansion = get_variable(varname);
+  }
+
+  // Finally, recursively expand any variable references in the
+  // variable's expansion.
+  ExpandedVariable new_var;
+  new_var._varname = varname;
+  new_var._next = expanded;
+  string result = r_expand_string(expansion, &new_var);
+
+  // And *then* apply any inline patsubst.
+  if (got_patsubst) {
+    vector<string> tokens;
+    tokenize(patsubst, tokens, VARIABLE_PATSUBST_DELIM);
+    
+    if (tokens.size() != 2) {
+      cerr << "inline patsubst should be of the form "
+	   << VARIABLE_PREFIX << VARIABLE_OPEN_BRACE << "varname"
+	   << VARIABLE_PATSUBST << PATTERN_WILDCARD << ".c"
+	   << VARIABLE_PATSUBST_DELIM << PATTERN_WILDCARD << ".o"
+	   << VARIABLE_CLOSE_BRACE << ".\n";
+    } else {
+      PPFilenamePattern from(tokens[0]);
+      PPFilenamePattern to(tokens[1]);
+    
+      if (!from.has_wildcard() || !to.has_wildcard()) {
+	cerr << "The two parameters of inline patsubst must both include "
+	     << PATTERN_WILDCARD << ".\n";
+	return string();
+      }
+    
+      // Split the expansion into tokens based on the spaces.
+      vector<string> words;
+      tokenize_whitespace(result, words);
+      
+      vector<string>::iterator wi;
+      for (wi = words.begin(); wi != words.end(); ++wi) {
+	(*wi) = to.transform(*wi, from);
+      }
+    
+      result = repaste(words, " ");
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_variable_nested
+//       Access: Private
+//  Description: Expands a variable reference of the form
+//               $[varname(scope scope scope)].  This means to
+//               concatenate the expansions of the variable in all of
+//               the named scopes.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_variable_nested(const string &varname, 
+		       const string &scope_names) const {
+  if (_named_scopes == (PPNamedScopes *)NULL) {
+    return string();
+  }
+
+  vector<string> names;
+  tokenize_whitespace(scope_names, names);
+
+  // Get all of the named scopes.
+  PPNamedScopes::Scopes scopes;
+  
+  vector<string>::const_iterator ni;
+  for (ni = names.begin(); ni != names.end(); ++ni) {
+    const string &name = (*ni);
+    _named_scopes->get_scopes(name, scopes);
+  }
+
+  if (scopes.empty()) {
+    return string();
+  }
+
+  // Now go through the scopes and build up the results.
+  vector<string> results;
+
+  PPNamedScopes::Scopes::const_iterator si;
+  for (si = scopes.begin(); si != scopes.end(); ++si) {
+    PPScope *scope = (*si);
+    string nested = scope->expand_variable(varname);
+    if (!nested.empty()) {
+      results.push_back(nested);
+    }
+  }
+
+  string result = repaste(results, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_wildcard
+//       Access: Private
+//  Description: Expands the "wildcard" function variable.  This
+//               returns the set of files matched by the parameters
+//               with shell matching characters.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_wildcard(const string &params) const {
+  vector<string> results;
+  glob_string(expand_string(params), results);
+
+  string result = repaste(results, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_isdir
+//       Access: Private
+//  Description: Expands the "isdir" function variable.  This
+//               returns true if the parameter exists and is a
+//               directory, or false otherwise.  This actually expands
+//               the parameter(s) with shell globbing characters,
+//               similar to the "wildcard" function, and looks only at
+//               the first expansion.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_isdir(const string &params) const {
+  vector<string> results;
+  glob_string(expand_string(params), results);
+
+  if (results.empty()) {
+    // No matching file, too bad.
+    return string();
+  }
+
+  const string &filename = results[0];
+  struct stat stbuf;
+
+  string result;
+  if (stat(filename.c_str(), &stbuf) == 0) {
+    if (S_ISDIR(stbuf.st_mode)) {
+      result = filename;
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_libtest
+//       Access: Private
+//  Description: Expands the "libtest" function variable.  This
+//               serves as a poor man's autoconf feature to check to
+//               see if a library by the given name exists on the
+//               indicated search path, or on the system search path.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_libtest(const string &params) const {
+  // Get the parameters out based on commas.  The first parameter is a
+  // space-separated set of directories to search, the second
+  // parameter is a space-separated set of library names.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() != 2) {
+    cerr << "libtest requires two parameters.\n";
+    return string();
+  }
+
+  vector<string> directories;
+  tokenize_whitespace(tokens[0], directories);
+
+  // Also add the system directories to the list, whatever we think
+  // those should be.  Here we have to make a few assumptions.
+#ifdef PLATFORM_WIN32
+  const char *windir = getenv("WINDIR");
+  if (windir != (const char *)NULL) {
+    directories.push_back(string(windir) + "\\System");
+    directories.push_back(string(windir) + "\\System32");
+  }
+
+  const char *lib = getenv("LIB");
+  if (lib != (const char *)NULL) {
+    tokenize(lib, directories, ";");
+  }
+#endif
+
+  // We'll also check the Unix standard places, even if we're building
+  // Windows, since we might be using Cygwin.
+
+  // Check LD_LIBRARY_PATH.
+  const char *ld_library_path = getenv("LD_LIBRARY_PATH");
+  if (ld_library_path != (const char *)NULL) {
+    tokenize(ld_library_path, directories, ":");
+  }
+
+  directories.push_back("/lib");
+  directories.push_back("/usr/lib");
+
+  vector<string> libnames;
+  tokenize_whitespace(tokens[1], libnames);
+
+  if (libnames.empty()) {
+    // No libraries is a default "false".
+    return string();
+  }
+
+  // We only bother to search for the first library name in the list.
+  string libname = libnames[0];
+
+  string found;
+
+#ifdef PLATFORM_WIN32
+  if (libname.length() > 4 && libname.substr(libname.length() - 4) == ".lib") {
+    found = find_searchpath(directories, libname);    
+    if (found.empty()) {
+      found = find_searchpath(directories, libname.substr(0, libname.length() - 4) + ".dll");
+    }
+  } else {
+    found = find_searchpath(directories, "lib" + libname + ".lib");
+    if (found.empty()) {
+      found = find_searchpath(directories, "lib" + libname + ".dll");
+    }
+  }
+  
+#else
+  found = find_searchpath(directories, "lib" + libname + ".a");
+  if (found.empty()) {
+    found = find_searchpath(directories, "lib" + libname + ".so");
+  }
+#endif
+
+
+  return found;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_bintest
+//       Access: Private
+//  Description: Expands the "bintest" function variable.  This
+//               serves as a poor man's autoconf feature to check to
+//               see if an executable program by the given name exists
+//               on the indicated search path, or on the system search
+//               path.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_bintest(const string &params) const {
+  // We only have one parameter: the filename of the executable.  We
+  // always search for it on the path.
+  string binname = expand_string(params);
+
+  if (binname.empty()) {
+    // No binary, no exist.
+    return string();
+  }
+
+  // An explicit path from the root does not require a search.
+#ifdef PLATFORM_WIN32
+  if ((binname.length() > 2 && binname[1] == ':') || binname[0] == '/')
+#else
+  if (binname[0] == '/')
+#endif
+    {
+    if (access(binname.c_str(), F_OK) == 0) {
+      return binname;
+    }
+    return string();
+  }
+
+  const char *path = getenv("PATH");
+  if (path == (const char *)NULL) {
+    // If the path is undefined, too bad.
+    return string();
+  }
+
+  string pathvar(path);
+
+  vector<string> directories;
+
+#ifdef PLATFORM_WIN32
+  if (pathvar.find(';') != string::npos) {
+    // If the path contains semicolons, it's a native Windows-style
+    // path: split it up based on semicolons.
+    tokenize(pathvar, directories, ";");
+
+  } else {
+    // Otherwise, assume it's a Cygwin-style path: split it up based
+    // on colons.
+    tokenize(pathvar, directories, ":");
+  }
+#else
+  tokenize(pathvar, directories, ":");
+#endif
+
+  string found;
+
+#ifdef PLATFORM_WIN32
+  found = find_searchpath(directories, binname + ".exe");
+  if (found.empty()) {
+    found = find_searchpath(directories, binname);
+  }
+  
+#else
+  found = find_searchpath(directories, binname);
+#endif
+
+  return found;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_shell
+//       Access: Private
+//  Description: Expands the "shell" function variable.  This executes
+//               the given command in a subprocess and returns the
+//               standard output.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_shell(const string &params) const {
+  string command = expand_string(params);
+  int pid, status;
+
+  int pd[2];
+  if (pipe(pd) < 0) {
+    // pipe() failed.
+    perror("pipe");
+    return string();
+  }
+
+  pid = fork();
+  if (pid < 0) {
+    // fork() failed.
+    perror("fork");
+    return string();
+  }
+    
+  if (pid == 0) {
+    // Child.
+    dup2(pd[1], STDOUT_FILENO);
+    char *argv[4];
+    argv[0] = "sh";
+    argv[1] = "-c";
+    argv[2] = (char *)command.c_str();
+    argv[3] = (char *)NULL;
+    execv("/bin/sh", argv);
+    exit(127);
+  }
+
+  // Parent.  Wait for the child to terminate, and read from its
+  // output while we're waiting.
+  close(pd[1]);
+  bool child_done = false;
+  bool pipe_closed = false;
+  string output;
+
+  while (!child_done && !pipe_closed) {
+    static const int buffer_size = 1024;
+    char buffer[buffer_size];
+    int read_bytes = (int)read(pd[0], buffer, buffer_size);
+    if (read_bytes < 0) {
+      perror("read");
+    } else if (read_bytes == 0) {
+      pipe_closed = true;
+    } else {
+      output += string(buffer, read_bytes);
+    }
+
+    if (!child_done) {
+      int waitresult = waitpid(pid, &status, WNOHANG);
+      if (waitresult < 0) {
+	if (errno != EINTR) {
+	  perror("waitpid");
+	  return string();
+	}
+      } else if (waitresult > 0) {
+	child_done = true;
+      }
+    }
+  }
+  close(pd[0]);
+
+  // Now get the output.  We split it into words and then reconnect
+  // it, to simulate the shell's backpop operator.
+  vector<string> results;
+  tokenize_whitespace(output, results);
+
+  string result = repaste(results, " ");
+
+  return result;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_firstword
+//       Access: Private
+//  Description: Expands the "firstword" function variable.  This
+//               returns the first of several words separated by
+//               whitespace.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_firstword(const string &params) const {
+  // Split the parameter into tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(expand_string(params), words);
+
+  if (!words.empty()) {
+    return words[0];
+  }
+  return string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_patsubst
+//       Access: Private
+//  Description: Expands the "patsubst" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_patsubst(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() < 3) {
+    cerr << "patsubst requires at least three parameters.\n";
+    return string();
+  }
+
+  if ((tokens.size() % 2) != 1) {
+    cerr << "subst requires an odd number of parameters.\n";
+    return string();
+  }
+
+  // Split the last parameter into tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(tokens.back(), words);
+
+  // Build up a vector of from/to patterns.
+  typedef vector<PPFilenamePattern> Patterns;
+  typedef vector<Patterns> FromPatterns;
+  FromPatterns from;
+  Patterns to;
+
+  size_t i;
+  for (i = 0; i < tokens.size() - 1; i += 2) {
+    // Each "from" pattern might be a collection of patterns separated
+    // by spaces.
+    from.push_back(Patterns());
+    vector<string> froms;
+    tokenize_whitespace(tokens[i], froms);
+    vector<string>::const_iterator fi;
+    for (fi = froms.begin(); fi != froms.end(); ++fi) {
+      PPFilenamePattern pattern(*fi);
+      if (!pattern.has_wildcard()) {
+	cerr << "All the \"from\" parameters of patsubst must include "
+	     << PATTERN_WILDCARD << ".\n";
+	return string();
+      }
+      from.back().push_back(pattern);
+    }
+
+    // However, the corresponding "to" pattern is just one pattern.
+    to.push_back(PPFilenamePattern(tokens[i + 1]));
+  }
+  size_t num_patterns = from.size();
+  assert(num_patterns == to.size());
+  
+  vector<string>::iterator wi;
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    bool matched = false;
+    for (i = 0; i < num_patterns && !matched; i++) {
+      Patterns::const_iterator pi;
+      for (pi = from[i].begin(); pi != from[i].end() && !matched; ++pi) {
+	if ((*pi).matches(*wi)) {
+	  matched = true;
+	  (*wi) = to[i].transform(*wi, (*pi));
+	}
+      }
+    }
+  }
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_filter
+//       Access: Private
+//  Description: Expands the "filter" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_filter(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() != 2) {
+    cerr << "filter requires two parameters.\n";
+    return string();
+  }
+
+  // Split up the first parameter--the list of patterns to filter
+  // by--into tokens based on the spaces.
+  vector<string> pattern_strings;
+  tokenize_whitespace(tokens[0], pattern_strings);
+
+  vector<PPFilenamePattern> patterns;
+  vector<string>::const_iterator psi;
+  for (psi = pattern_strings.begin(); psi != pattern_strings.end(); ++psi) {
+    patterns.push_back(PPFilenamePattern(*psi));
+  }
+
+  // Split up the second parameter--the list of words to filter--into
+  // tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(tokens[1], words);
+
+  vector<string>::iterator wi, wnext;
+  wnext = words.begin();
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    const string &word = (*wi);
+
+    bool matches_pattern = false;
+    vector<PPFilenamePattern>::const_iterator pi;
+    for (pi = patterns.begin(); 
+	 pi != patterns.end() && !matches_pattern; 
+	 ++pi) {
+      matches_pattern = (*pi).matches(word);
+    }
+
+    if (matches_pattern) {
+      *wnext++ = word;
+    }
+  }
+
+  words.erase(wnext, words.end());
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_filter_out
+//       Access: Private
+//  Description: Expands the "filter_out" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_filter_out(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() != 2) {
+    cerr << "filter requires two parameters.\n";
+    return string();
+  }
+
+  // Split up the first parameter--the list of patterns to filter
+  // by--into tokens based on the spaces.
+  vector<string> pattern_strings;
+  tokenize_whitespace(tokens[0], pattern_strings);
+
+  vector<PPFilenamePattern> patterns;
+  vector<string>::const_iterator psi;
+  for (psi = pattern_strings.begin(); psi != pattern_strings.end(); ++psi) {
+    patterns.push_back(PPFilenamePattern(*psi));
+  }
+
+  // Split up the second parameter--the list of words to filter--into
+  // tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(tokens[1], words);
+
+  vector<string>::iterator wi, wnext;
+  wnext = words.begin();
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    const string &word = (*wi);
+
+    bool matches_pattern = false;
+    vector<PPFilenamePattern>::const_iterator pi;
+    for (pi = patterns.begin(); 
+	 pi != patterns.end() && !matches_pattern; 
+	 ++pi) {
+      matches_pattern = (*pi).matches(word);
+    }
+
+    if (!matches_pattern) {
+      *wnext++ = word;
+    }
+  }
+
+  words.erase(wnext, words.end());
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_subst
+//       Access: Private
+//  Description: Expands the "subst" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_subst(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() < 3) {
+    cerr << "subst requires at least three parameters.\n";
+    return string();
+  }
+
+  if ((tokens.size() % 2) != 1) {
+    cerr << "subst requires an odd number of parameters.\n";
+    return string();
+  }
+
+  // Split the last parameter into tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(tokens.back(), words);
+  
+  vector<string>::iterator wi;
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    string &word = (*wi);
+
+    // Check for the given word in the subst/replace strings.
+    bool found = false;
+    for (size_t i = 0; i < tokens.size() - 1 && !found; i += 2) {
+      if (tokens[i] == word) {
+	found = true;
+	word = tokens[i + 1];
+      }
+    }
+  }
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_sort
+//       Access: Private
+//  Description: Expands the "sort" function variable: sort the words
+//               into alphabetical order, and also remove duplicates.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_sort(const string &params) const {
+  // Split the string up into tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(expand_string(params), words);
+
+  sort(words.begin(), words.end());
+  words.erase(unique(words.begin(), words.end()), words.end());
+
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_unique
+//       Access: Private
+//  Description: Expands the "unique" function variable: remove
+//               duplicates from the list of words without changing
+//               the order.  The first appearance of each word
+//               remains.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_unique(const string &params) const {
+  // Split the string up into tokens based on the spaces.
+  vector<string> words;
+  tokenize_whitespace(expand_string(params), words);
+
+  vector<string>::iterator win, wout;
+  set<string> included_words;
+
+  win = words.begin();
+  wout = words.begin();
+  while (win != words.end()) {
+    if (included_words.insert(*win).second) {
+      // This is a unique word so far.
+      *wout++ = *win;
+    }
+    ++win;
+  }
+
+  words.erase(wout, words.end());
+  string result = repaste(words, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_eq
+//       Access: Private
+//  Description: Expands the "if" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_if(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() == 2) {
+    if (!tokens[0].empty()) {
+      return tokens[1];
+    } else {
+      return "";
+    }
+  } else if (tokens.size() == 2) {
+    if (!tokens[0].empty()) {
+      return tokens[1];
+    } else {
+      return tokens[2];
+    }
+  }
+
+  cerr << "if requires two or three parameters.\n";
+  return string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_eq
+//       Access: Private
+//  Description: Expands the "eq" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_eq(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() != 2) {
+    cerr << "eq requires two parameters.\n";
+    return string();
+  }
+
+  string result;
+  if (tokens[0] == tokens[1]) {
+    result = "1";
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_ne
+//       Access: Private
+//  Description: Expands the "ne" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_ne(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() != 2) {
+    cerr << "ne requires two parameters.\n";
+    return string();
+  }
+
+  string result;
+  if (!(tokens[0] == tokens[1])) {
+    result = "1";
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_not
+//       Access: Private
+//  Description: Expands the "not" function variable.  This returns
+//               nonempty if its argument is empty, empty if its
+//               argument is nonempty.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_not(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  if (tokens.size() != 1) {
+    cerr << "not requires two parameters.\n";
+    return string();
+  }
+
+  string result;
+  if (tokens[0].empty()) {
+    result = "1";
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_or
+//       Access: Private
+//  Description: Expands the "or" function variable.  This returns
+//               nonempty if any of its arguments are nonempty.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_or(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  vector<string>::const_iterator ti;
+  for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
+    if (!(*ti).empty()) {
+      return "1";
+    }
+  }
+  return "";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_and
+//       Access: Private
+//  Description: Expands the "and" function variable.  This returns
+//               nonempty if all of its arguments are nonempty.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_and(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, true);
+
+  vector<string>::const_iterator ti;
+  for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
+    if ((*ti).empty()) {
+      return "";
+    }
+  }
+  return "1";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_upcase
+//       Access: Private
+//  Description: Expands the "upcase" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_upcase(const string &params) const {
+  string result = expand_string(params);
+  string::iterator si;
+  for (si = result.begin(); si != result.end(); ++si) {
+    (*si) = toupper(*si);
+  }
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_downcase
+//       Access: Private
+//  Description: Expands the "downcase" function variable.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_downcase(const string &params) const {
+  string result = expand_string(params);
+  string::iterator si;
+  for (si = result.begin(); si != result.end(); ++si) {
+    (*si) = tolower(*si);
+  }
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_closure
+//       Access: Private
+//  Description: Expands the "closure" function variable.  This is a
+//               special function that recursively expands a map
+//               variable with the given parameter string until all
+//               definitions have been encountered.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_closure(const string &params) const {
+  // Split the string up into tokens based on the commas.
+  vector<string> tokens;
+  tokenize_params(params, tokens, false);
+
+  if (tokens.size() != 2) {
+    cerr << "closure requires two parameters.\n";
+    return string();
+  }
+
+  // The first parameter is the map variable name, the second
+  // parameter is the expression to close.
+  string varname = expand_string(tokens[0]);
+  string expression = tokens[1];
+
+  const MapVariableDefinition &def = find_map_variable(varname);
+  if (&def == &_null_map_def) {
+    cerr << "Warning:  undefined map variable: " << varname << "\n";
+    return string();
+  }
+
+  // Now evaluate the expression within this scope, and then again
+  // within each scope indicated by the result, and then within each
+  // scope indicated by *that* result, and so on.  We need to keep
+  // track of the words we have already evaluated (hence the set), and
+  // we also need to keep track of all the partial results we have yet
+  // to evaluate (hence the vector of strings).
+  set<string> closure;
+  vector<string> partial_results;
+
+  partial_results.push_back(expand_string(expression));
+
+  while (!partial_results.empty()) {
+    // Pull off one of the partial results (it doesn't matter which
+    // one), and chop it up into its constituent words.
+    vector<string> pass;
+    tokenize_whitespace(partial_results.back(), pass);
+    partial_results.pop_back();
+
+    // And then map each of those words into scopes.
+    vector<string>::const_iterator wi;
+    for (wi = pass.begin(); wi != pass.end(); ++wi) {
+      const string &word = (*wi);
+      bool inserted = closure.insert(word).second;
+      if (inserted) {
+	// This is a new word, which presumably maps to a scope.  What
+	// does the expression evaluate to within that scope?
+	
+	MapVariableDefinition::const_iterator mvi;
+	mvi = def.find(word);
+	if (mvi != def.end()) {
+	  PPScope *scope = (*mvi).second;
+	  partial_results.push_back(scope->expand_string(expression));
+	}
+      }
+    }
+  }
+
+  // Now we have the complete transitive closure of $[mapvar expression].
+  vector<string> results;
+  set<string>::const_iterator ci;
+  for (ci = closure.begin(); ci != closure.end(); ++ci) {
+    results.push_back(*ci);
+  }
+
+  string result = repaste(results, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_map_variable
+//       Access: Private
+//  Description: Expands a map variable function reference.  This
+//               looks up the given keys in the map and expands the
+//               first parameter for each corresponding scope.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_map_variable(const string &varname, const string &params) const {
+  // Split the string up into tokens based on the commas, but don't
+  // expand the variables yet.
+  vector<string> tokens;
+  tokenize_params(params, tokens, false);
+
+  if (tokens.size() != 2) {
+    cerr << "map variable expansions require two parameters: $["
+	 << varname << " " << params << "]\n";
+    return string();
+  }
+
+  // Split the second parameter into tokens based on the spaces.  This
+  // is the set of keys.
+  vector<string> keys;
+  tokenize_whitespace(expand_string(tokens[1]), keys);
+
+  return expand_map_variable(varname, tokens[0], keys);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::expand_map_variable
+//       Access: Private
+//  Description: Expands a map variable function reference.  This
+//               looks up the given keys in the map and expands the
+//               expression for each corresponding scope.
+////////////////////////////////////////////////////////////////////
+string PPScope::
+expand_map_variable(const string &varname, const string &expression,
+		    const vector<string> &keys) const {
+  const MapVariableDefinition &def = find_map_variable(varname);
+  if (&def == &_null_map_def) {
+    cerr << "Warning:  undefined map variable: " << varname << "\n";
+    return string();
+  }
+
+  vector<string> results;
+
+  // Now build up the set of expansions of the expression in the
+  // various scopes indicated by the keys.
+  vector<string>::const_iterator wi;
+  for (wi = keys.begin(); wi != keys.end(); ++wi) {
+    MapVariableDefinition::const_iterator di;
+    di = def.find(*wi);
+    if (di != def.end()) {
+      PPScope *scope = (*di).second;
+      string expansion = scope->expand_string(expression);
+      if (!expansion.empty()) {
+	results.push_back(expansion);
+      }
+    }
+  }
+
+  string result = repaste(results, " ");
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::find_map_variable
+//       Access: Private
+//  Description: Looks for the map variable definition in this scope
+//               or some ancestor scope.
+////////////////////////////////////////////////////////////////////
+const PPScope::MapVariableDefinition &PPScope::
+find_map_variable(const string &varname) const {
+  const MapVariableDefinition &def = p_find_map_variable(varname);
+  if (&def != &_null_map_def) {
+    return def;
+  }
+
+  // No such map variable.  Check the stack.
+  ScopeStack::reverse_iterator si;
+  for (si = _scope_stack.rbegin(); si != _scope_stack.rend(); ++si) {
+    const MapVariableDefinition &def = (*si)->p_find_map_variable(varname);
+    if (&def != &_null_map_def) {
+      return def;
+    }
+  }
+
+  // Nada.
+  return _null_map_def;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::p_find_map_variable
+//       Access: Private
+//  Description: The implementation of find_map_variable() for a
+//               particular static scope, without checking the stack.
+////////////////////////////////////////////////////////////////////
+const PPScope::MapVariableDefinition &PPScope::
+p_find_map_variable(const string &varname) const {
+  MapVariables::const_iterator mvi;
+  mvi = _map_variables.find(varname);
+  if (mvi != _map_variables.end()) {
+    return (*mvi).second;
+  }
+
+  if (_parent_scope != (PPScope *)NULL) {
+    return _parent_scope->find_map_variable(varname);
+  }
+
+  return _null_map_def;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPScope::glob_string
+//       Access: Private
+//  Description: Expands the words in the string as if they were a set
+//               of filenames using the shell globbing characters.
+//               Fills up the results vector (which the user should
+//               ensure is empty before calling) with the set of all
+//               files that actually match the globbing characters.
+////////////////////////////////////////////////////////////////////
+void PPScope::
+glob_string(const string &str, vector<string> &results) const {
+  vector<string> words;
+  tokenize_whitespace(str, words);
+
+  vector<string>::const_iterator wi;
+
+  glob_t pglob;
+  memset(&pglob, 0, sizeof(pglob));
+
+  int flags = 0;
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    glob((*wi).c_str(), flags, NULL, &pglob);
+    flags |= GLOB_APPEND;
+  }
+
+  for (int i = 0; i < (int)pglob.gl_pathc; i++) {
+    results.push_back(string(pglob.gl_pathv[i]));
+  }
+
+  globfree(&pglob);
+}

+ 123 - 0
ppremake/ppScope.h

@@ -0,0 +1,123 @@
+// Filename: ppScope.h
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPSCOPE_H
+#define PPSCOPE_H
+
+#include "ppremake.h"
+
+#include <map>
+#include <vector>
+
+class PPNamedScopes;
+class PPDirectoryTree;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPScope
+// Description : Defines a (possibly nested) scope for variable
+//               definitions.  Variables may be defined in a
+//               system-wide variable file, in a template file, or in
+//               an individual source file.
+////////////////////////////////////////////////////////////////////
+class PPScope {
+public:
+  PPScope(PPNamedScopes *named_scopes);
+
+  PPNamedScopes *get_named_scopes() const;
+
+  void set_parent(PPScope *parent);
+  PPScope *get_parent() const;
+
+  void define_variable(const string &varname, const string &definition);
+  bool set_variable(const string &varname, const string &definition);
+  void define_map_variable(const string &varname, const string &definition);
+  void define_map_variable(const string &varname, const string &key_varname,
+			   const string &scope_names);
+
+  string get_variable(const string &varname) const;
+  string expand_variable(const string &varname) const;
+
+  PPDirectoryTree *get_directory() const;
+  void set_directory(PPDirectoryTree *directory);
+
+  string expand_string(const string &str) const;
+  string expand_self_reference(const string &str, const string &varname) const;
+
+  static void push_scope(PPScope *scope);
+  static PPScope *pop_scope();
+  static PPScope *get_bottom_scope();
+
+private:
+  class ExpandedVariable {
+  public:
+    string _varname;
+    ExpandedVariable *_next;
+  };
+
+  typedef map<string, PPScope *> MapVariableDefinition;
+
+  bool p_set_variable(const string &varname, const string &definition);
+  bool p_get_variable(const string &varname, string &result) const;
+
+  void tokenize_params(const string &str, vector<string> &tokens,
+		       bool expand) const;
+
+  string r_expand_string(const string &str, ExpandedVariable *expanded) const;
+  string r_scan_variable(const string &str, size_t &vp) const;
+  string r_expand_variable(const string &str, size_t &vp,
+			   PPScope::ExpandedVariable *expanded) const;
+  string expand_variable_nested(const string &varname, 
+				const string &scope_names) const;
+
+  string expand_wildcard(const string &params) const;
+  string expand_isdir(const string &params) const;
+  string expand_libtest(const string &params) const;
+  string expand_bintest(const string &params) const;
+  string expand_shell(const string &params) const;
+  string expand_firstword(const string &params) const;
+  string expand_patsubst(const string &params) const;
+  string expand_filter(const string &params) const;
+  string expand_filter_out(const string &params) const;
+  string expand_subst(const string &params) const;
+  string expand_sort(const string &params) const;
+  string expand_unique(const string &params) const;
+  string expand_if(const string &params) const;
+  string expand_eq(const string &params) const;
+  string expand_ne(const string &params) const;
+  string expand_not(const string &params) const;
+  string expand_or(const string &params) const;
+  string expand_and(const string &params) const;
+  string expand_upcase(const string &params) const;
+  string expand_downcase(const string &params) const;
+  string expand_closure(const string &params) const;
+  string expand_map_variable(const string &varname, const string &params) const;
+  string expand_map_variable(const string &varname, const string &expression,
+			     const vector<string> &keys) const;
+
+  const MapVariableDefinition &
+  find_map_variable(const string &varname) const;
+  const MapVariableDefinition &
+  p_find_map_variable(const string &varname) const;
+
+  void glob_string(const string &str, vector<string> &results) const;
+
+  PPNamedScopes *_named_scopes;
+
+  PPDirectoryTree *_directory;
+
+  typedef map<string, string> Variables;
+  Variables _variables;
+
+  typedef map<string, MapVariableDefinition> MapVariables;
+  MapVariables _map_variables;
+  static MapVariableDefinition _null_map_def;
+
+  PPScope *_parent_scope;
+  typedef vector<PPScope *> ScopeStack;
+  static ScopeStack _scope_stack;
+};
+
+
+#endif

+ 48 - 0
ppremake/ppSubroutine.cxx

@@ -0,0 +1,48 @@
+// Filename: ppSubroutine.cxx
+// Created by:  drose (10Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppSubroutine.h"
+
+PPSubroutine::Subroutines PPSubroutine::_subroutines;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPSubroutine::define_sub
+//       Access: Public, Static
+//  Description: Adds a subroutine to the global list with the
+//               indicated name.  The subroutine pointer must have
+//               been recently allocated, and ownership of the pointer
+//               will be passed to the global list; it may later
+//               delete it if another subroutine is defined with the
+//               same name.
+////////////////////////////////////////////////////////////////////
+void PPSubroutine::
+define_sub(const string &name, PPSubroutine *sub) {
+  Subroutines::iterator si;
+  si = _subroutines.find(name);
+  if (si == _subroutines.end()) {
+    _subroutines.insert(Subroutines::value_type(name, sub));
+  } else {
+    delete (*si).second;
+    (*si).second = sub;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PPSubroutine::get_sub
+//       Access: Public, Static
+//  Description: Returns the previously-defined subroutine with the
+//               given name, or NULL if there is no such subroutine
+//               with that name.
+////////////////////////////////////////////////////////////////////
+const PPSubroutine *PPSubroutine::
+get_sub(const string &name) {
+  Subroutines::const_iterator si;
+  si = _subroutines.find(name);
+  if (si == _subroutines.end()) {
+    return NULL;
+  } else {
+    return (*si).second;
+  }
+}

+ 34 - 0
ppremake/ppSubroutine.h

@@ -0,0 +1,34 @@
+// Filename: ppSubroutine.h
+// Created by:  drose (10Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PPSUBROUTINE_H
+#define PPSUBROUTINE_H
+
+#include "ppremake.h"
+
+#include <vector>
+#include <map>
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : PPSubroutine
+// Description : This represents a named subroutine defined via the
+//               #defsub .. #end sequence that may be invoked at any
+//               time via #call.  All subroutine definitions are
+//               global.
+////////////////////////////////////////////////////////////////////
+class PPSubroutine {
+public:
+  vector<string> _lines;
+
+public:
+  static void define_sub(const string &name, PPSubroutine *sub);
+  static const PPSubroutine *get_sub(const string &name);
+
+  typedef map<string, PPSubroutine *> Subroutines;
+  static Subroutines _subroutines;
+};
+
+#endif
+

+ 124 - 0
ppremake/ppremake.cxx

@@ -0,0 +1,124 @@
+// Filename: ppremake.cxx
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "ppremake.h"
+#include "ppMain.h"
+#include "ppScope.h"
+
+#ifdef HAVE_GETOPT
+#include <getopt.h>
+#else
+#include <gnu_getopt.h>
+#endif
+
+static void
+usage() {
+  cerr <<
+    "\n"
+    "ppremake [opts] subdir-name [subdir-name..]\n"
+    "ppremake\n"
+    "\n"
+    "This is Panda pre-make: a script preprocessor that scans the directory\n"
+    "hierarchy beginning at root-directory, looking for directories that\n"
+    "contain a file called " SOURCE_FILENAME ".  At the top of the directory\n"
+    "tree must be a file called " PACKAGE_FILENAME ", which should define\n"
+    "key variable definitions for processing, as well as pointing out the\n"
+    "locations of further config files.\n\n"
+
+    "The package file is read and interpreted, followed by each source file\n"
+    "in turn; after each source file is read, the template file (specified in\n"
+    "the config file) is read.  The template file  contains the actual statements\n"
+    "to be output and will typically be set up to generate Makefiles or whatever\n"
+    "is equivalent and appropriate to the particular build environment in use.\n\n"
+
+    "The parameters are the names of the subdirectories (their local names, not\n"
+    "the relative or full paths to them) that are to be processed.  All\n"
+    "subdirectories (that contain a file named " SOURCE_FILENAME ") will be\n"
+    "scanned, but only the named subdirectories will have output files\n"
+    "generated.  If no parameter is given, then all directories will be\n"
+    "processed.\n\n"
+
+    "Options:\n\n"
+
+    "  -h           Display this page.\n"
+    "  -V           Report the version of ppremake, and exit.\n"
+    "  -P           Report the current platform name, and exit.\n\n"
+    "  -p platform  Build as if for the indicated platform name.\n\n";
+}
+
+static void
+report_version() {
+  cerr << "This is " << PACKAGE << " version " << VERSION << ".\n";
+}
+
+static void
+report_platform() {
+  cerr << "ppremake built for platform " << PLATFORM << ".\n";
+}
+
+int
+main(int argc, char *argv[]) {
+  extern char *optarg;
+  extern int optind;
+  const char *optstr = "hVPp:";
+
+  string platform = PLATFORM;
+  int flag = getopt(argc, argv, optstr);
+
+  while (flag != EOF) {
+    switch (flag) {
+    case 'h':
+      usage();
+      exit(0);
+
+    case 'V':
+      report_version();
+      exit(0);
+      break;
+
+    case 'P':
+      report_platform();
+      exit(0);
+      break;
+
+    case 'p':
+      platform = optarg;
+      break;
+
+    default:
+      exit(1);
+    }
+    flag = getopt(argc, argv, optstr);
+  }
+
+  argc -= (optind-1);
+  argv += (optind-1);
+
+  PPScope global_scope((PPNamedScopes *)NULL);
+  global_scope.define_variable("PROGRAM", PACKAGE);
+  global_scope.define_variable("PROGVER", VERSION);
+  global_scope.define_variable("PLATFORM", platform);
+
+  PPMain ppmain(&global_scope);
+  if (!ppmain.read_source(".")) {
+    exit(1);
+  }
+
+  if (argc < 2) {
+    if (!ppmain.process_all()) {
+      exit(1);
+    }
+  } else {
+    for (int i = 1; i < argc; i++) {
+      if (!ppmain.process(argv[i])) {
+	cerr << "Unable to process " << argv[i] << ".\n";
+	exit(1);
+      }
+    }
+  }
+
+  cerr << "No errors.\n";
+  return (0);
+}

+ 51 - 0
ppremake/ppremake.h

@@ -0,0 +1,51 @@
+/*
+// Filename: ppremake.h
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+*/
+
+#ifndef PPREMAKE_H
+#define PPREMAKE_H
+
+#include "config.h"
+
+#ifdef __cplusplus
+#ifdef HAVE_IOSTREAM
+#include <iostream>
+#include <fstream>
+#else
+#include <iostream.h>
+#include <fstream.h>
+#endif
+
+#include <string>
+
+#ifdef HAVE_NAMESPACE
+using namespace std;
+#endif
+#endif /* __cplusplus */
+
+#define PACKAGE_FILENAME "Package.pp"
+#define SOURCE_FILENAME "Sources.pp"
+
+#define DIRECTORY_SEPARATOR '/'
+#define COMMAND_PREFIX '#'
+#define VARIABLE_PREFIX '$'
+#define VARIABLE_OPEN_BRACE '['
+#define VARIABLE_CLOSE_BRACE ']'
+#define PATTERN_WILDCARD '%'
+#define BEGIN_COMMENT "//"
+
+#define FUNCTION_PARAMETER_SEPARATOR ','
+
+#define VARIABLE_OPEN_NESTED '('
+#define VARIABLE_CLOSE_NESTED ')'
+#define VARIABLE_PATSUBST ":"
+#define VARIABLE_PATSUBST_DELIM "="
+
+#define SCOPE_DIRNAME_SEPARATOR '/'
+#define SCOPE_DIRNAME_WILDCARD "*"
+#define SCOPE_DIRNAME_CURRENT "."
+
+#endif

+ 134 - 0
ppremake/tokenize.cxx

@@ -0,0 +1,134 @@
+// Filename: tokenize.cxx
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "tokenize.h"
+
+#include <ctype.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: tokenize
+//  Description: Chops the source string up into pieces delimited by
+//               any of the characters specified in delimiters.
+//               Repeated delimiter characters represent zero-length
+//               tokens.
+//
+//               It is the user's responsibility to ensure the output
+//               vector is cleared before calling this function; the
+//               results will simply be appended to the end of the
+//               vector.
+////////////////////////////////////////////////////////////////////
+void
+tokenize(const string &source, vector<string> &tokens,
+	 const string &delimiters) {
+  size_t p = 0;
+  while (p < source.length()) {
+    size_t q = source.find_first_of(delimiters, p);
+    if (q == string::npos) {
+      tokens.push_back(source.substr(p));
+      return;
+    }
+    tokens.push_back(source.substr(p, q - p));
+    p = q + 1;
+  }
+  tokens.push_back(string());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: tokenize_whitespace
+//  Description: Chops the source string up into pieces delimited by
+//               whitespace characters.  It is different from
+//               tokenize() in that repeated whitespace characters are
+//               not significant.
+//
+//               It is the user's responsibility to ensure the output
+//               vector is cleared before calling this function; the
+//               results will simply be appended to the end of the
+//               vector.
+////////////////////////////////////////////////////////////////////
+void
+tokenize_whitespace(const string &source, vector<string> &tokens) {
+  // First, start at the first non-whitespace character.
+  size_t p = 0;
+  while (p < source.length() && isspace(source[p])) {
+    p++;
+  }
+
+  while (p < source.length()) {
+    // Now scan to the end of the word.
+    size_t q = p;
+    while (q < source.length() && !isspace(source[q])) {
+      q++;
+    }
+    tokens.push_back(source.substr(p, q - p));
+    p = q;
+
+    while (p < source.length() && isspace(source[p])) {
+      p++;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: repaste
+//  Description: Returns a string representing the given sequence of
+//               tokens concatenated together with the separator
+//               string between them.
+////////////////////////////////////////////////////////////////////
+string
+repaste(const vector<string> &tokens, const string &separator) {
+  string result;
+  if (!tokens.empty()) {
+    vector<string>::const_iterator ti;
+    ti = tokens.begin();
+    result += (*ti);
+    ++ti;
+
+    while (ti != tokens.end()) {
+      result += separator;
+      result += (*ti);
+      ++ti;
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: trim_blanks
+//  Description: Returns a new string, equivalent to the source
+//               string, but with the leading and trailing whitespace
+//               removed.
+////////////////////////////////////////////////////////////////////
+string
+trim_blanks(const string &str) {
+  size_t p = 0;
+  while (p < str.length() && isspace(str[p])) {
+    p++;
+  }
+ 
+  size_t q = str.length();
+  while (q > p && isspace(str[q - 1])) {
+    q--;
+  }
+
+  return str.substr(p, q - p);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: contains_whitespace
+//  Description: Returns true if the string contains any whitespace
+//               characters, false if it does not.
+////////////////////////////////////////////////////////////////////
+bool
+contains_whitespace(const string &str) {
+  string::const_iterator si;
+  for (si = str.begin(); si != str.end(); ++si) {
+    if (isspace(*si)) {
+      return true;
+    }
+  }
+
+  return false;
+}

+ 30 - 0
ppremake/tokenize.h

@@ -0,0 +1,30 @@
+// Filename: tokenize.h
+// Created by:  drose (25Sep00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TOKENIZE_H
+#define TOKENIZE_H
+
+#include "ppremake.h"
+
+#include <vector>
+
+// A couple of handy functions for breaking up a string into tokens,
+// and repasting the tokens back into a string.
+
+void tokenize(const string &source, vector<string> &tokens,
+	      const string &delimiters);
+
+void tokenize_whitespace(const string &source, vector<string> &tokens);
+
+string repaste(const vector<string> &tokens, const string &separator);
+
+// And this is just handy to have.
+string trim_blanks(const string &str);
+
+// So is this.
+bool contains_whitespace(const string &str);
+
+#endif
+