The following steps are executed when ppremake is invoked: 1. Look in the current directory for a file called Package.pp. If it is not found, but Sources.pp is present in the current directory, then look for Package.pp in the parent directory. Repeat until Package.pp is located; this is the root of the source directory hierarchy. 2. Read and interpret the Package.pp file. This should, in turn, read in (via the #include directive; see below) whatever other configuration scripts are relevant to the current package. The exact behavior of Package.pp is not hardcoded into ppremake, but the Package.pp scripts for each of the Panda directories will ultimately include the following files in order: Any Package.pp for a dependent tree (e.g. $PANDA/Package.pp, etc.) $DTOOL/Package.pp $DTOOL/Config.pp $DTOOL/Config.Platform.pp The user's Config.pp file, as specified by $PPREMAKE_CONFIG. $DTOOL/pptempl/System.pp The various Package.pp files are designed to cascade down, such that when you invoke ppremake from a high-level tree, such as Direct (for instance), ppremake invokes $DIRECT/Package.pp, which in turn invokes $PANDA/Package.pp, which in turn invokes $DTOOL/Package.pp. At the bottom level, $DTOOL/Package.pp does most of the remaining setup work, in particular including the various Config.pp files, and System.pp. The Config.pp files are intended to define the variables the user might customize for a particular build environment; the user's personal Config.pp file is included last, and may override any of the default values defined in the system Config.pp files. One particularly critical variable that is defined by Config.pp is $[BUILD_TYPE]. This represents the type of makefile rules that are to be generated, e.g. "unix" to generate standard Unix-style makefile syntax, or "msvc" to generate syntax specific to Microsoft's nmake. The particular string specified here controls the selection of which Template files are to be loaded in a later step to actually generate output files in each source directory. The System.pp file is also essential and defines a few more variables that ppremake will be looking for during its execution. This tells ppremake the names of certain critical files that it needs to include. The use of System.pp to define these filenames, instead of hardcoding them into ppremake, is consistent with the ppremake philosophy of letting as much of its behavior as possible be controlled from within the scripting language itself. 3. Once Package.pp has been read, traverse the entire source hierarchy, and read each Sources.pp file. A separate named scope is defined for each Sources.pp file; the name of the scope is taken from the name of the directory containing the file. 4. Read in the global variable declarations. This is loaded from a particular file specified by System.pp; this file defines global declarations that will be useful when processing each Template file later. To save time, this file is read once and stored in a global scope, rather than re-read with each Template file. The particular filename is specified by the ppremake variable $[GLOBAL_FILE], which was set by System.pp; normally this is $DTOOL/pptempl/Global.pp. $DTOOL/pptempl/Global.pp will, in turn, also read in the file $DTOOL/pptempl/Global.$[BUILD_TYPE].pp if it exists, which may specify further global declarations that are specific to the particular build type in effect. Remember, $[BUILD_TYPE] was specified when Config.pp was read, and controls the particular type of makefiles or build scripts that are to be generated. 5. Build the inter-directory dependency chain. This is the chain of relationships between different directories within the source hierarchy. Each directory may depend on code from zero or more other directories within the same package. (That is, a directory may include header files defined within another directory, and/or require linking with a library built in that directory.) This inter-directory dependency is critical to determine things such as build order. If one directory must be built before another directory, the makefiles must be constructed with this relationship explicit. This relationship is determined for each directory by executing the script named by $[DEPENDS_FILE] (this variable was set by System.pp) within each source directory scope. Remember, each Sources.pp file has already been read in, and assigned its own unique named scope. The DEPENDS_FILE script is responsible for defining the variable $[DEPEND_DIRS] to contain the space-separated list of directories (that is, named scopes) that the files in this current directory depend on. Normally, System.pp sets $[DEPENDS_FILE] to $DTOOL/pptempl/Depends.pp. This script builds up $[DEPEND_DIRS] based on the $[get_depend_libs] function which is defined in Global.pp, which is in turn based on variables like $[LOCAL_LIBS] defined within the Sources.pp file. The inter-directory dependency information can be accessed from within ppremake scripts simply by examining the $[DEPEND_DIRS] variable within the appropriate scope. In addition, the directory relationships are automatically used to sort the variables $[SUBDIR] and $[SUBTREE] in order, such that each directory is listed after the directories it depends on. There is one other variable which should be defined by the DEPENDS_FILE script: $[DEPENDABLE_HEADERS] should list the source files within the current directory that may be named as #include files in other files. This is used to build file-level dependencies to support the $[dependencies] built-in function, and is not related to inter-directory dependencies. 6. Finally, generate the actual output files in each source directory. This is done by executing the script named by $[TEMPLATE_FILE] (set by System.pp) within each source directory scope. Normally, System.pp sets $[TEMPLATE_FILE] to $DTOOL/pptempl/Template.$[BUILD_TYPE].pp. Recall again that $[BUILD_TYPE] was specified when Config.pp was read, and controls the particular type of makefiles or build scripts that are to be generated, so there is a different Template script file for each supported build type. The Template file is responsible for generating the actual makefile (or whatever build script is required) as appropriate to each source directory. It now has all the information required: all source directories have been read, so the relationships between different source directories can control the output; also, the inter-directory and inter-file dependencies are available. Generally, a Template file will include at least one #output directive to generate the appropriate makefile for this directory. Since there may be different kinds of source directories, each with very different makefile requirements, the Template files usually have one large #if condition based on the value of $[DIR_TYPE], which is set within each Sources.pp (and is set in System.pp to default to "src").