5.1. How Software Gets BuiltThis section is a brief overview of how source code is turned into an executable, a program that can actually run on a computer. This is the process is known as "building software." A summary of the different stages of a build is shown in the next section, Section 5.1.1. This summary is also used later on when considering how different build tools work in practice. Source code (or "the program") is what developers (or "programmers") write. Source code can be written in high-level programming languages (such as C or Java), scripting or dynamic languages (such as Perl or Python), or low-level languages (such as assembly code). Source code can also be binary files such as images or precompiled libraries. Loosely speaking, anything needed by your product that cannot be generated from another place within your project is part of the source for your project. A build is the process in which a build tool uses other tools to convert the source code into a working product that can be used by other people. How a product is used by other people varies for different customers and different machines. Some languages (such as Perl) are interpreted, which means that the source code is used directly by software that's already on the machine. This existing software is called an interpreter. Other languages (such as C and Java) are compiled, which means that another tool called a compiler converts the source code to the appropriate binary file format for the CPU to execute on each particular machine. Writing source code is relatively straightforward until the amount of source code begins to grow. To help you keep track of what's going on in your program, you really want to divide up the source code. "Put the GUI code in these files, put the disk access code over here in this file, and then put all the database interface code into all these other files," and so on. Each of these parts depend on some of the other parts, but they probably don't depend on all of the other parts, and so dividing up the code in this way makes the product easier to imagine. Products depend upon these other parts being present, either when the product is compiled (at compile time) or when the program is run (at runtime). To reduce the number of dependencies between different parts of the program, all kinds of simple and complex mechanisms have been invented over the years. Some of the commonly used ones are header files, data encapsulation, and interfaces. Section 5.3, later in this chapter, goes into more details. What all these approaches have in common is an attempt to make it very clear to different parts of the program how to use the other parts. These ideas do indeed help reduce the number of interfaces between different parts of a program, but at the cost of having to update them as the program grows. The problem of building programs starts to get harder when different parts of the program have to be built in a certain order. For instance, with C programs you commonly build .o or .obj object files and then combine them into library files, before linking the generated files together to create an executable. With Java, you have to build .class files before you can create a .jar file. Before running your Java program, you'll also have to make sure that any other required .class or .jar files are present on your machine. Whatever the particular requirements are for building the different parts of a program in a certain order, you might think that making sure that all those different steps are performed in the right order shouldn't be too hard. After all, it's just like a recipe for a meal with a large number of ingredients and lots of complicated steps to follow in order. Build tools are designed to perform the specified steps, using a defined build processthe recipewhich is usually described in build files. Once the recipe has been defined, then it just needs to be followed by the build tool. If a program is not being changed, then even the simplest build tools should be able to follow a well-defined build process. However, change is inevitable in any program that is being developed or maintained. Changing just one source file means that the changed file has to be rebuilt. After that, other files that depend on the changed file have to be rebuilt. (The structure of which files depend on which is known as the dependency tree.) Shifting up to the next conceptual gear, the list of files that depend on the changed file also changes over time. That is, the parts of a program that need to be rebuilt for a particular change is not constant. Once the build tool has worked out which parts of the programs need rebuilding, it has to execute the appropriate commands to build just those parts. These commands may also have their own required order (e.g., compile before linking). Table 5-1 describes some of the ways that source code can change and what the build tools have to do to deal with the changes.
Some concrete examples of the different types of changes shown in Table 5-1 are as follows ("foo.c foo.o" means that foo.o depends on foo.c):
5.1.1. The Different Stages of a BuildBuilds are made up of a number of different stages, just as compilers can have preprocessing, compiling, and linking stages. Each stage is usually performed by the same build tool, though not always. For example, configuration and the calculation of dependencies may use separate tools, sometimes even using the compiler itself. In practice, some of these build stages are small or nonexistent with some build tools, but the order of the stages is usually the same for all build tools. The sequence of the different stages for a typical build tool is:
Some of the stages may be repeated during a build. For instance, if source files are generated by executing some build commands, their dependencies will also need to be calculated. Likewise, the actual build commands that are executed may be constructed piece by piece. 5.1.2. A Typical Build FileExample 5-1 shows a generic build file, showing how some dependencies are specified; others will be discovered by the build tool. For example, the build tool does not specify what other files are required by fileA. Example 5-1. A build file# The executable myproduct is made up of fileA and fileB and uses # libraryX as well executable("myproduct", "fileA, fileB", "libraryX") # The library named libraryX is made up of file1 and file2 library("libraryX", "file1, file2") # This list of tests comprises two files named test_alice and test_bob # which are defined in some other build file files("tests", "test_alice, test_bob") # Installation targets which specify the files that are built for each # different kind of user install("testers", "myproduct, tests") install("customer", "myproduct") |