Team Development with Visual Studio .NET and Visual SourceSafe
The Build Process
Summary: This chapter explains the role of the build server and automated build script that is used to generate system builds. A sample build script is provided to highlight the important concepts.
This is Chapter 5 of the Team Development with Visual Studio® .NET and Visual SourceSafe guide. Start here to get the full picture.
This chapter will help you:
- Manage versioning and dependency relationships.
- Automate builds using the appropriate scripts and tools.
- Organize and distribute build output.
The build process is a critical element for all software development projects. Do not be tempted to get by without oneparticularly in a team development environment. You should configure a build server and create the necessary build scripts as early as possible in the development cyclecertainly well before you are ready to begin integration testing.
The main function of a build script is to provide an automated way to generate system builds in a repeatable and consistent manner. Under normal circumstances, you schedule the build script to run at night, to avoid placing unwanted stress on the build server, Microsoft® Visual SourceSafe (VSS) server and the network during development hours.
The build script accesses VSS by using the VSS automation model or by calling VSS from the command line. It labels and extracts the latest versions of source and project files and then it uses Microsoft Visual Studio® .NET (by executing devenv.exe from the command line) to build your solution or solutions. The build script typically builds both a debug and release version of your system.
Handling Dependency Relationships
In single solution or partitioned single solution systems, project references take care of dependencies and build order. The build script for these types of systems is simple because it can execute Devenv.exe once against a single solution.
The build script is more complex for multi-solution systems because it must build solutions in the correct order, based on known dependency relationships. This is to ensure that when an assembly is updated (and its version changes), all client assemblies that reference it are also updated and rebuilt against the latest version.
An assembly's version is specified via the AssemblyVersion attribute (which is defined within the AssemblyInfo.cs or AssemblyInfo.vb file). The version number is physically represented as a four part number separated with periods:
<major version>.<minor version>.<build number>.<revision>
You do not have to set and update each part explicitly because you can use wild characters (*) to automatically generate the build and revision numbers. Visual Studio .NET generates an AssemblyInfo source file with the AssemblyVersion attribute defined as follows:
The result of this is a build number set to the number of days since a random, designated start date and the revision based on the number of seconds since midnight.
You can either use auto-increment version numbers or opt to manually control version numbers as part of the build process. Each approach has associated benefits and drawbacks.
Note For a Microsoft Visual Basic® .NET project with an AssemblyVersion set to "1.0.*", the assembly version is only updated the first time the project is rebuilt within the Visual Studio .NET integrated development environment (IDE). The version number remains constant for subsequent rebuilds within the same instance of Visual Studio .NET. This does not represent a problem because the assembly version is for information only in assemblies that do not have a strong name. For strong named assemblies, you should avoid the use of wild characters in the AssemblyVersion attribute, as explained in the following section.
For C# projects with an AssemblyVersion set to "1.0.*", the assembly version is updated every time the project is rebuilt.
Using Auto-Increment Version Numbers
An auto-increment version number is established by adopting the default "1.0.*" pattern for the AssemblyVersion attribute.
Using auto-increment version numbers has the following advantages:
- The build and revision numbers are handled automatically by Visual Studio .NET and do not have to be handled by the build script or build coordinator.
- You guarantee never to have different builds of an assembly with the same version number.
Using auto-increment version numbers has the following disadvantages:
- The internal assembly build number does not match your system build number, which means there is no easy way to correlate a particular assembly with the build that generated it. This may be particularly problematic when you need to support your system in a production environment.
- Build and revision numbers are not increased by one but are based on the time an assembly is built.
- A new version of an assembly is generated each time it is built regardless of whether any changes have been made to the assembly. For strongly named assemblies, this means that all clients of that assembly must also be rebuilt to point to the correct version. However, if the build process rebuilds the whole system this should not be an issue.
Using Static Version Numbers
With this approach, you use a static version number, for example "1.0.1001.1", and update the major or minor numbers only when a new version is shipped with your next system release.
Using static version numbers has the following advantages:
- You have complete control over the exact version number.
- Assembly build numbers can be synchronized with the system build number.
Using static version numbers has the following disadvantages:
- The version numbers must be manually updated by the build coordinator or by the build script.
- If the version is not incremented with every build, you may end up with multiple builds of the same assembly with the same strong name. This is undesirable and can be problematic for assemblies that are installed in the Global Assembly Cache (GAC).
Important If you do not change the version of a strongly named assembly and attempt to install it into the GAC using the Microsoft Windows® operating system Installer, the latest dynamic-link library (DLL) does not install if a previous version exists in the GAC with the same version number.
If you use Gacutil.exe instead of Windows Installer to install the assembly, the updated DLL is installed even if the assembly version number is the same.
Consider Centralizing Assembly Version Numbers
To update the assembly version information for multiple assemblies, the build script or build coordinator must check out and update multiple AssemblyInfo files. To centralize the version information and enable a single file checkout and update, consider the following approach:
- Place the AssemblyVersion attribute in a single source file, for example AssemblyVersionInfo.cs or AssemblyVersionInfo.vb.
- Share the file across any projects that need to share the same version number within VSS.
This approach assumes that you want the same assembly version assigned to all assemblies generated by a particular system build. This may or may not be appropriate for your particular system.
Build Server Folder Structure
You should set up the same basic folder structure on the build server as your development workstations. Therefore, the basic folder structure discussed within Use a Consistent Folder Structure for Solutions and Projects in Chapter 3, "Structuring Solutions and Projects," remains the recommended structure for the build server.
Consider Maintaining Previous Builds
You may also want to create a folder structure based on build numbers to maintain the output from previous system builds. The benefit of this is that it allows the test team to easily install and test specific versions of your system without being affected by ongoing development changes.
The build server folder structure is illustrated in Figure 5.1:
Note the following points from Figure 5.1:
- Builds are organized by build number. A simple approach for generating build numbers is described later in this chapter.
- The Latest folder always contains the output from the latest buildthis matches the binaries contained in the current highest build number folder.
- The contents of each project's output folder is copied by the build script to a subfolder beneath the Latest\Release folder, the name of which is based on the project name.
- In a multi-solution system, you can reference assemblies built in other solutions from folders beneath the Latest\Release folder. These folders are repopulated after each build; this guarantees that you always reference the most up-to-date assemblies with the latest version numbers.
- If isolated (and potentially disconnected) development is required within a multi-solution system, developers copy the build output to their local workstations and reference local assemblies by using a virtual drive letter.
Don't Alter the Build Output Path
You might be tempted to alter the output paths of your projects in order to build to a single folder and then establish file references to that folder. Do not do this for the following reasons:
- It causes the build process to fail with file lock errors when a referenced assembly exceeds 64 KB in size. This problem is likely to be fixed in a future version of Visual Studio .NET.
- You can encounter another problem when you reference an assembly from a folder that is designated as your project's output folder. In this event, Visual Studio .NET cannot take a local copy of the assembly because the source and destination folders for the copy operation are the same. If you subsequently remove the reference from your project, Visual Studio .NET deletes the "local" working copy. Because the local folder is the same as the assembly's original folder, the original assembly is deleted.
The Build Script
Figure 5.2 illustrates the individual steps that should be performed by your build script:
Figure 5.2. The Build Process
The following subsections describe the main steps performed by the build process.
Generating Build Version Numbers
Successive builds are identified by unique build numbers. Build numbers are used to name the output folder that contains the results of a particular build and to label source files within VSS.
Build numbers are generally incremental numbers that are unique across the system. There are many ways to generate build numbers. One simple approach is to maintain a text file in the Latest folder whose file name represents the latest build number; for example, 2021.txt. The build script can access this file to generate the next number.
Labeling Source Files
It's important to establish a relationship between a particular build and the set of project and source files that are used to generate that build. The simplest way to do this is to label the latest source file set with the current build number.
This allows you to recreate the exact build at any time in the future if the need arises. It also helps when you need to resolve a broken build, as described later in this chapter.
The following script fragment shows how to use the VSS command line to label a set of files within a given VSS project folder. To label all source files within your system, the top level VSS folder should be set to $/Projects/SystemName.
' Run VSS with the Label command with options to disable UI and answer ' Yes to any prompts. Specify output log file location, label to write ' and VSS project to label. Label is recursive therefore only one ' command is needed against the top-level VSS project. ' Current version number is held within the Version variable WSHShell.Run "ss Label -I-Y -O@..\SSafeOut_Label.txt -LVersion1." & Version & SSafeProj, 1, true
Extracting the Latest Source File Set
The build script performs a get operation to extract the latest set of source files from VSS. It uses the assigned label to pull the correct set of source files. Source files are extracted into the common folder structure on the build server, as illustrated earlier in Figure 5.1.
The following script fragment shows how to perform the get operation by using the VSS command line.
' Current version number is maintained in the Version variable WSHShell.Run "ss Get -I-Y -O@..\SSafeOut_Get.txt -VLVersion1." & Version & SSafeProj, 1, true
Creating a New Latest Folder
The build process always copies generated assemblies into the Latest folder, to ensure that any file references used within multi-solution systems resolve to the latest assembly versions.
The build script initially renames the current Latest folder to LatestBackup and then creates a new (and empty) Latest folder. This allows the script to roll back to the output of the previous build if the current one fails.
Building Solutions with Devenv.exe
The script builds individual solutions by executing Devenv.exe, supplying at minimum the solution file name, the /rebuild switch (which ensures all output and temporary output files are deleted at the start) and the required solution configuration. The build script should execute Devenv.exe twice, once to generate a release build and once to generate a debug build. The following script fragment shows how to execute Devenv.exe.
' Call devenv with the rebuild option (clean up previous build ' leftovers), the specified build configuration, the out option to ' enable logging to a file and finally the solution file to build ' specified via the command line. A count is used when deriving the ' log file name to ensure a separate log is generated for each solution ' build in a multi-solution system WSHShell.Run "devenv /rebuild " & Config & " /out buildoutput" &Count& ".txt " & SolutionName, 1, true
The build script parses the output build log generated by Devenv.exe to see whether or not the latest solution build was successful.
For multi-solution systems that include cross solution file references, the release build of a particular solution must always be generated before the debug build to ensure that any file references can be resolved. This assumes that you set file references to the Release version of assemblies as advocated within Always Reference Release Builds with File References in Chapter 4, "Managing Dependencies."
Copying Output to the Latest Folder
Single solution or partitioned-single solution systems do not require a Latest folder because you use project references rather than file references to refer to inner-system assemblies. For these types of system, the build script can simply archive assembly output beneath the relevant build number folder.
For multi-solution systems however, after each solution is built, the output assemblies for all constituent projects must be copied to the Latest\Release or Latest\Debug folders in order to guarantee that projects that are built subsequently within other solutions reference the latest assembly versions.
Note The build script can use the number of solutions passed via the command line to infer whether or not it is dealing with a multi-solution system. If the solution count is greater than one, it must copy assembly output to the Latest folder. Otherwise, it can omit this step.
Organize Assembly Output beneath the Latest Folder
Assemblies are actually copied to subfolders beneath Latest\Release and Latest\Debug, the names of which are based on the project names. The reason that this approach is preferred to placing all output assemblies directly into the Latest\Release folder is that it clearly organizes the output and it allows two versions of outer system assemblies (such as third-party assemblies) with the same name to be included in different projects.
Remember that outer system assemblies are not built as part of the build process and should be included in the projects that need to reference them. It is possible (although unlikely) that two projects reference different versions of the third-party assembly (with the same name). If you separate build output into distinct subfolders, you can ensure that these DLLs do not overwrite one another when the build script copies its output to the Latest folder.
Copying the Latest Folder to a Build Number Folder
If the build of a multi-solution system is successful, the contents of the Latest folder are copied to a folder named with the current build number. This allows successive builds to be archived.
Renaming the Latest Folder as LatestBroken
If the build fails, the build script renames the Latest folder as LatestBroken and reinstates the LatestBackup folder as the Latest folder. This allows any developers who are currently referencing the Latest folder on the build server to continue with their local development.
Resolving a Broken Build
The build coordinator must work to resolve a broken build as quickly as possible. Typically one or more source files are at fault and these either need to be updated within VSS or the latest changes need to be backed out all together.
If you update source files within VSS, you must manually label them with the current build number using VSS Explorer. This ensures that the updated files are associated with the correct build.
After you update the relevant files and you want to rerun the current build, the build script should be supplied with the current build number as a command line argument. The absence of a build number signifies a new build. If a build number is supplied, the number (which matches the source code labels) can be used to extract the relevant source files from VSS.
Rebuilding Multi-Solution Systems
While a build is running, you may experience problems compiling locally if you are working on a multi-solution system that uses file reference to reference assemblies on the build server because the build process starts by clearing the Latest folder.
To avoid this issue, you should switch to an isolated development model as described in Consider an Isolated Development Approach in Chapter 4, "Managing Dependencies," particularly when working on multi-solution systems. In this way, you are protected from builds that may run during the day.
Emailing Build Results
Build results should be sent via e-mail to the development team for successful and unsuccessful builds. The e-mail message should include the build log as an attachment to enable developers to help diagnose build failures or identify warnings generated from their code.
To avoid hard coding e-mail aliases in the build script, you should set up distribution lists based on project or solution names.
Packaging the Build
To facilitate the installation of the latest build into the test (or development) environment, the build script can package the output within a Microsoft Installer (MSI) file.
For single solution (or partitioned single solution) systems, you can include a Visual Studio .NET Setup and Deployment project within the solution. This can be used to automatically generate an MSI file as part of the solution build.
Note You can prevent the Setup and Deployment project from building each time you build the solution on your local workstation by excluding it through the Configuration Manager (on the Build menu) within Visual Studio .NET. If you exclude a project from a solution build using this method, you do not affect the source controlled solution file. Changes are maintained within the solution user option file, which is developer specific and not under source control.
Whenever new projects are added to your solution you must remember to update and configure the deployment project to ensure that the output of the new project is included within the MSI file and that any project specific installation steps are performed. This is generally the responsibility of the build coordinator, who should be informed by developers when new projects are added. All developers must be familiar with this process, so make sure it's part of your development process guidelines.
For multi-solution systems, things are once again more complex because a single Setup and Deployment project can be used only to package the output generated by projects in a single solution. In this scenario, the build script can generate an MSI by using third party software or the Windows Installer software development kit (SDK).
Creating Build Script Accounts
You must create a Windows account for the specific purposes of executing the build script on the build server. This account must have access to the shared folder or folders on the VSS server that host the VSS databases.
You must also create a build script user within each of the VSS databases that the build script requires access to. This is used by the script while accessing the VSS database.
For more information about automation, see Visual SourceSafe 6.0 Automation.
This is Chapter 5 of the Team Development with Visual Studio .NET and Visual SourceSafe guide. To read the next chapter, please see Chapter 6, "Working with Visual SourceSafe."