Dependency Analysis

In this section are investigated ways to specify which modules form a program and, when some modules or interfaces are modified, to determine which modules need be recompiled.

Each module in a program or library may export public procedures, variables and data types in one or more interfaces. Client modules, which need the exported functionality, will import the corresponding interface.

A number of modules linked together may form a library. In that case, the list of modules to group into the library must be supplied. Modules and perhaps some libraries linked together may form a program. The starting point of the program must be identified, usually by the reserved name Main.

The list of modules forming a program might be inferred by looking recursively at the interfaces imported by the main module and imported by the modules associated with these interfaces. Additional information may be required however. Indeed, some of the required modules may reside in separate directories or in libraries. Therefore, the list of directories and libraries to search needs to be specified.

There are further cases where this information is not sufficient. Other modules not imported by any of the modules reached from the Main module may still affect the program. Indeed, the initialization code associated with a module may call procedures in modules reached from the Main module and affect the value of some global variables used elsewhere in the program. Finally, some programming environments allow connecting to procedures at run time. For instance, some environments allow reading persistent objects from disk and will reconnect any pointers to procedures to the needed procedures in the program. In such cases, the complete list of modules required for a program cannot be deduced automatically but needs to be specified by the programmer.

Program development usually requires numerous recompilations, each time after corrections are made to a small subset of the modules. Dependency analysis must be performed to determine which modules need to be recompiled. Indeed, recompiling everything each time is wasteful; leaving the programmer decide which module needs recompilation is error prone and leads to frustrating situations when the compiled program does not correspond to the source code.

A simple form of dependency analysis is performed in makefiles. There, the files used for a program are listed along with their dependencies and the actions needed to produce the derived files. For example, the executable program depends on all the object files and is produced by linking them together. Each object file depends on the corresponding source file, and the imported interfaces, and is produced by the compiler. Some versions of makefiles also account for the compilation options used. To rebuild a program, it is checked if the options changed or if any file is older than files on which it depends, in which case the associated action is triggered. Thus, if a source file is newer than the corresponding object file, it gets recompiled and the program gets relinked because the new object file is more recent than the executable than depends on it.

For large programs, however, finer dependency analysis may reduce the number of modules to recompile each time. Furthermore, it is interesting if the list of dependencies, especially between a module and the imported interfaces, are deduced automatically. Some systems, like m3build for Modula-3, will store with each compiled module the list of elements (type declaration, variable, procedure...) on which they depend. When an interface that a module imports changes, this list can be used to determine if the change indeed impacts on this module. In many cases, this finer grained dependency analysis can reduce by a large amount the number of modules recompiled.

Below is a sample makefile. The make program is a fairly general dependency analysis tool, not limited to building C or C++ programs. For each component, the dependents are listed after the colon and the associated action is supplied on the following line.



Main.o: Main.c Second.h
        cc -c Main.c

Second.o: Second.h
        cc -c Second.c

Main: Main.o Second.o
        cc -o Main Main.o Second.o

The following is a m3makefile which is used by m3build, a tool developed to perform fine grained dependency analysis of Modula-3 programs. Each component is identified as only a module (implementation) or a module and an interface (module) and the name of the produced executable is supplied (program).


implementation("Main") 
module("Second")
program("Main")


Copyright 1995 Michel Dagenais, dagenais@vlsi.polymtl.ca, Wed Mar 8 14:41:03 EST 1995