Philip Brown introduces his column and describes how to ensure your applications are compiled correctly.
Standardising Application Builds
Hopefully I'll be able to make this an interesting and stimulating series, exploring many facets of Delphi development. Firstly, let me introduce myself. I'm an IT consultant and active programmer for nearly two decades, just in time to catch the demise of the punched card and herald the advent of relatively accessible and cheap computing power. Over these years I have used many languages and it is my belief that Delphi is the best general-purpose Windows development tool currently available, with an excellent blend of powerful constructs, user interface tools and support for strong object-oriented development techniques.
No silver bullets
It is a feature of our industry that many vendors try to sell us "silver bullet" solutions to application development. We are continually bombarded with information attempting to persuade us that if we use this company's tool, that company's middleware or the next company's database, then all of the issues around application development will disappear. Overnight, we will be able to produce complex systems on time and within budget, even in the presence of inadequate design documentation and changing user requirements. Over the years high level languages, software engineering, structured methodologies, 4GL's, object orientation, client/server, middleware, rapid application design and Java have all been touted by their evangelists as complete solutions to software development - the list goes on. Bitter experience shows that there is no magic solution and it is down to us, as application designers and developers, to accept that in a world where clients have increasingly high expectations for ever more complex systems, we must continually strive to improve the quality of our work. These days, few applications live in isolation and must interact with other systems in complex ways. Developing modern applications is getting harder rather than easier and it is wrong to suppose that any one approach provides the answer.
My specific interest, which I hope to explore in this and future columns, is to promote the development of better Delphi applications. Assume two independent programming teams are given the same brief to develop an application from a complete systems requirement document, and that both produce a bug-free program (note that we're already in the realms of software development fantasy). Can two different but functionally identical programs be separated in terms of quality? What makes one application better than another?
In practice, every single decision made during the development will have an impact on the quality of the program. User interface decisions will affect how user-friendly (or hostile) the program is. Apparently simple things like buttons with similar functions having the same name throughout the application can have a fundamental impact on how readily users will become familiar the operation of the program. The choice of database, ease of installation, compatibility with other applications, even the layout of code within units and variable naming conventions (or the absence of them) can all have a significant impact or the perceived or measurable concept of how good an application is. Developers and users will all form opinions as to the relative quality of a given application, without necessarily being able to pinpoint or justify why they have taken this view. Assessing the quality of a program encompasses virtually every single aspect and implementation decision made, and the aim of this column is to introduce and discuss ways by which the quality of delivered systems can be significantly increased. This will cover such concepts as user interface style, data entry and validation, persistent storage of information, and a large amount of time will be spent discussing program architecture.
Studies have shown that the single biggest contribution over the last twenty years towards increasing program quality is the concept of data hiding. Object orientation is the purest expression so far of this concept and this column will promote good OO techniques throughout. We will explore the construction of truly object oriented systems and demonstrate how they lead to applications that are significantly quicker to develop and easier to maintain. Along the way we will challenge accepted ways of writing Delphi programs and demonstrate why alternative approaches are often better.
Consistency is king
If there is a single way of assessing the quality of a program, my definition would be "Is it consistent?" Consistency can be applied to virtually all aspects of a program, and in nearly every case it is better to be more consistent than less. Consistent with what? Well, it depends on what is being evaluated. Consider the user interface: does each form appear screen centred, or are some left at design time co-ordinates? Are all buttons the same size and appear in a consistent horizontal or vertical plane? Is every "OK" button set as the default for the form (and are any labelled "Ok")? Do all forms respond correctly when the user changes Windows preferences for font size and colour schemes? For that matter, does the application look and behave like other Windows applications? Are toolbar icons the same as those in other applications that perform a similar function? Do they all have a tooltip? Are similar components used for the same function throughout the application or is an eclectic mixture of edit boxes, masked edit, calendars and date/time picker controls all used for the user input of dates?
A similar set of questions can be targeted at all other aspects of the system. Do all tables and fields have similar naming conventions? Is the source code indentation standard the same in every unit? In every case, it is preferable for these factors to be similar throughout the application. It matters much less exactly which standards have been chosen, as long as whichever standard is selected is ubiquitous (although there are often better choices than others, and these will be discussed).
One small, though very important, factor in considering consistency is ensuring that you always deliver the same product to your clients (with new features intact and some bugs removed, hopefully). Fundamental to this process is the concept of a build. This is defined as preparing all deliverables to a known state before shipping them to a client. For the most trivial applications the deliverable might be a single executable, but for more significant systems a build might include a number of executables, dynamic link libraries, help file, resource files, configuration information, database tables and so on. How can we ensure that all of these files are created correctly in each build so that any changes made since the last build are reflected appropriately? Just loading the application into Delphi and forcing a Build All does not guarantee that you will always deliver an appropriate application suite.
Considering just the situation for Delphi executables, compiler settings might have been changed since the last time you made a release for your client. If you forget to change those settings back (such as disabling debug symbols), you will deliver a fundamentally different application. Sometimes this will not matter, but if your application relies upon a particular stack size or optimisation option to operate correctly then you may encounter significant issues.
Every application for public release should have a documented build process, preferably one that is completely automated. The size of the application and number of files to be produced will dictate the complexity of this process; for simple applications a simple batch file can be all that is required, but in extreme situations a dedicated environment may need to be provided (the build process for Windows NT used to take the best part of day on a quad-processor machine).
In the Delphi world there are a number of options for automating most build processes: a batch file (DOS or Windows Script) that calls the Delphi command-line compiler, using a "make" utility or a custom application dedicated to building a specific suite of deliverables.
Inprise supply a version of the venerable "make" utility with Delphi. "Make" utilities operate from a text file that describes the source files on which a target depends, and the command(s) required to compile them. In the early days of programming in C, the use of "make" utilities was fundamental in improving the quality of released applications. However, one of the main benefits of "make" was that it provided a way to describe the interdependencies of C source files, something which the Delphi (Pascal) "uses" clause do very well. Therefore, learning the somewhat arcane syntax of the files on which "make" depend has fewer benefits for Delphi programmers.
For many systems written with Delphi, a single batch file will suffice for a build process. This batch file will contain all of the commands necessary to ensure that a complete system is compiled. This should contain not only commands to compile resource files and Delphi projects, but also to ensure that the version control database is complete (you are using source code control, aren't you?)
Figure 1 shows an example DOS batch script for compiling a particular project, showing some of the types of entries typical in a build process. The first part of the script ensures that all source code units have been checked back into the version control database - a vital stage to prevent compiling a project while developers have not made their changes globally available. The version control system shown here is SourceSafe, but virtually all commercial-grade systems can be driven from the command line.
After ensuring all files have been checked in to the source code system, the complete source code is fetched and stored in a new directory tree on the machine performing the build. Obviously, to build the latest version of the program all of the latest files are required. An interesting point here is that it is recommended that if the machine performing the build is also a developer's workstation, then a different directory tree is used to hold the source code rather than the one used by the developer. The reason for this is that the build will typically compile the application with no debug information and most optimisations on. If the directory path were shared with the development environment then the programmer would need to remember to issue a Build All to avoid linking in units that would be difficult to debug.
Having fetched all of the latest source code, the application(s) should then be compiled. For Delphi projects the command line version of the compiler (found in the Delphi bin directory) called dcc32 should be used. This version of the compiler recognises many parameters to allow full control from the command line. Alternatively, a set of compiler options can be controlled from a configuration file (.CFG) with the same name as the project itself, which is often a more visible way of controlling compilation. Note that Delphi 5 overwrites this file from the development environment, another good reason for keeping the build source directory separate from that for development. There is a particularly useful flag (-E) for dcc32 which relocates the target file to a new directory, typically pointing to a new directory that will contain just the compiled files that constitute the build.
Of course, many systems consist of additional files such as resource files, help files and so on. You can use whatever command line tools are appropriate to compile the target files; again, Delphi provides a command-line compiler for resource files in brcc32.
A DOS batch file or Windows Script may be adequate for smaller systems, but more complex ones requiring complex logic and control may benefit from having their own build process written as a custom application. Such applications may shell out to the command line versions of compiler tools, but often drive them through a COM interface that provides more flexibility and control. In either case the concept of the build process is the same - to guarantee that all target files are compiled into a known and consistent state. You can build whatever measures you want into your build process - such as notifying the development or QA team by email that the build succeeded and that a new application suite is ready for testing or delivery.
It is a good policy to perform a build at known and regular intervals. Knowing when a build is due allows the development team to work towards that goal and ensure that a consistent set of units are checked into the version control suite. Unless your build process takes so long that it would be inappropriate, it is a good rule to prevent any work on the application until the build is complete. After the build has finished, it is usual to require all developers to get the latest version of all units to ensure that they do not work from out of date source code. The concept behind a build is that it does not form part of the development and testing process - all builds are expected to compile correctly without errors and generally without warnings. "Breaking the build" by submitting code changes that then cause the build process to fail should be regarded as a heinous crime to be corrected at the earliest opportunity.
All applications written for general release should have their own automated build process, guaranteeing that a consistent set of deliverables is prepared every time a release is made. Next month, we'll look at value-added features that can be put into the build process to facilitate the tracking of many versions of programs co-existing "in the wild".
Figure 1 - Example DOS Batch script to build single executable and resource file
Figure 2 - Example .CFG file controlling Delphi application compilation
Next in series