Chapter 14. Building Software for Multiple Architectures

This chapter addresses the challenge of using a single source tree to develop an application for a variety of hardware/software platforms. We discuss various approaches, contrasting their advantages and disadvantages. An extended example incorporates some of the approaches.

Issues in Multiple-Architecture Development

The following issues arise in an environment where developers are creating and maintaining several architecture-specific variants of an application:

  • Different source code is required for different variants — Different UNIX-based operating systems may use different functions to implement the same task (for example, strchr(3) vs. index(3)). Likewise, it may be necessary to include different header files for different variants (for example, string.h vs. strings.h).

  • Different build procedures are required for different variants — The build procedures for different platforms vary. The differences might involve such particulars as compiler locations, compiler options, and libraries.

  • Builds for different variants must be kept separate — Since there is a single source tree, care must be taken to ensure that object modules and executables for one architecture do not become confused with those for other architectures. For example, the link editor must not try to create an IRIX–5 executable using an object module that was built for SunOS–4.

The following sections discuss and compare approaches to these issues. There are additional issues to be addressed in situations where ClearCase itself does not run on one of the target platforms. See Chapter 15, “Setting Up a Build on a Non-ClearCase Host,” for a discussion of one such issue.

Handling Source Code Differences

We recommend that you use the same files (that is, the same versions of file elements) in all builds, for all platforms. You can usually achieve this goal using the standard UNIX approach: conditional compilation using the C preprocessor, cpp(1). If header file string.h is to be used for the architecture whose cpp symbol is ARCH_A, and header file strings.h is to be used for architecture ARCH_B, use this code:

#ifdef ARCH_A
#include <string.h>
#else
#ifdef ARCH_B
#include <strings.h>
#endif /* ARCH_B */
#endif /* ARCH_A */

If a file element is not amenable to conditional compilation (for example, a bitmap image), the traditional solution is to put architecture-specific code in different elements altogether (for example, panel.image.sparc vs. panel.image.mc68k). This approach requires that build scripts be made architecture-specific, too.

With ClearCase, you also have the alternative of splitting the element into branches. The ARCH_A variant might be developed on the element's /main/arch_a branch; edits and builds for that variant would be performed in a view configured with this rule:

element * /main/arch_a/LATEST

Other variants would be developed on similarly-named branches, each using a different view, configured with a rule like the one above. In such a situation, the element's main branch might not be used at all.

We recommend that you use this branching strategy sparingly, because of these disadvantages:

  • Each time platform-independent code is changed on one of the branches, you may need to merge the change to all the other branches.

  • Developers must remember to set their views' config specs in an architecture-specific manner. In each view, only one variant of the application can be built.

Handling Build Procedure Differences

Ideally, a single file (that is, a single version of a file element) will drive all architecture-specific builds. One way to accomplish this is to revise makefiles as follows:

  • regularize build scripts

  • replace architecture-specific constructs (for example, /bin/cc) with make-macros (for example, a $(CC) macro)

  • use clearmake's include directive to incorporate architecture-specific settings of the make-macros

For example, suppose that source file main.c is compiled in different ways for the SunOS–4 and IRIX–5 variants:

main.o                                              (SunOS–4)
      /usr/5bin/cc -c -fsingle main.c
main.o:                                              (IRIX–5)
      /usr/bin/cc -c main.c

To “merge” these two build scripts, abstract the compiler pathname and the compiler options into make-macros, CC and CFLAGS. Then, place an architecture-specific include at the beginning of the makefile:

include /usr/project/make_macros/$(BLD_ARCH)_macros
 .
 .
main.o:
       $(CC) -c $(CFLAGS) main.c

The files in the make_macros directory would have these contents:

CC      = /usr/5bin/cc   /usr/project/make_macros/sun4_macros
CFLAGS  = -fsingle
CC      = /usr/bin/cc   /usr/project/make_macros/irix5_macros
CFLAGS  =

The make-macro BLD_ARCH acts as a selector between these two files. The value of this macro might be placed in an environment variable by a shell startup script:

setenv BLD_ARCH `uname -s`

Alternatively, developers might specify the value at build time:

clearmake main BLD_ARCH="IRIX5"

Alternative Approach, Using `imake'

The imake utility, distributed with many UNIX variants and available free-of-charge from MIT, provides an alternative to the scheme described in the preceding section. The imake methodology also involves architecture-specific make-macros, but in a different way. imake generates an architecture-specific makefile by running cpp on an architecture-independent template file, typically named imakefile.

A typical imakefile contains a series of cpp macros, each of which expands to a build target line and its corresponding multiline build script. Typically, the expansion itself is architecture-independent:

MakeObjectFromSrc(main)                (macro in `imakefile')
                               (expansion in actual makefile)
main.o: $(SRC)/main.c
       $(CC) -c $(CFLAGS) $(SRC)/main.c

imake places architecture-specific make-macro settings at the top of the generated makefile. For example:

SRC    = ..
CC     = /usr/5bin/cc
CFLAGS = -fsingle
RM     = rm -f

An idiosyncrasy of imake usage is that makefiles are derived objects, not source files. The architecture-independent template file (imakefile) is the source file, and should maintained as a ClearCase element.

Segregating the Derived Objects of Different Variants

It is essential to keep derived objects (object modules, executables) built for different architectures separate. This section describes two approaches, though others are possible.

Approach 1: Use Architecture-Specific Subdirectories

Each variant of an application can be built in its own subdirectory of the source directory. For example, if executable monet's source files are located in directory /usr/monet/src, then the variants might be built in subdirectories /usr/monet/src/sun4, /usr/monet/src/irix5, and so on. It is simplest to have the makefile create view-private subdirectories for this purpose. But if you wish to use different derived object storage pools for the different variants, you must create the sudirectories as elements (mkdir command) and then adjust their storage pool assignments (chpool command).

Since the derived objects for the different variants are built at different pathnames (for example, /usr/monet/src/sun4/main.o), they are guaranteed to be segregated by variant, and clearmake will never wink-in an object built for another architecture.

This approach has several advantages:

  • All variants of the application can be built in a single view.

  • It eliminates the burden of having to consider whether wink-in should be suppressed for some or all targets.

  • Since the derived objects for different variants have different pathnames, it is easier to organize multiple-architecture releases.

But this approach may have the disadvantage of requiring build script changes: the binaries for a build are no longer in the source directory, but in a subdirectory. Note, however, that the build script in <Emphasis>Alternative Approach, Using `imake' is structured for just this situation:

main.o: $(SRC)/main.c
 .
 .

Approach 2: Use Different Views

Perform builds for different platforms in different views (sun4_bld_vu, irix_bld_vu, and so on). A group of developers working on the same variant can share a view, or each can work in his or her own architecture-specific view.

In most cases, the build script that creates a derived object varies from variant to variant, as discussed in “Handling Build Procedure Differences”. If so, clearmake automatically prevents wink-in of derived objects built for another architecture. If this is not the case, force the build script to be architecture-specific by including a well-chosen message or comment. For example, if BLD_ARCH is used as described the “Handling Build Procedure Differences” section, you might include this message:

@echo "Building $@ for $(BLD_ARCH)"

This approach has the disadvantage that when an element is checked out, the developer can build only one variant of the application. Since the checked-out version is visible only in one view, builds of other variants (which take place in other views) do not “see” the checked-out version. The developer must checkin the element before building other variants.

Another disadvantage of this approach is a “combinatoric explosion” — if seven developers all wish to maintain their own views in which to build four variants, 28 views are required.

Multiple-Architecture Example, Using `imake'

The remainder of this chapter presents an example of multiple-architecture development. This example uses imake to support building in architecture-specific subdirectories.

Scenario

We will show how to set up multiple-architecture development in the /proj/monet/src directory. A developer will be able to perform a build for a particular architecture as follows:

  1. She logs into a machine of the desired architecture — for example, a workstation running SunOS 4.1.3.

  2. In her regular view, she goes to the source directory, /proj/monet/src, and enters a command to have imake generate a Makefile.

  3. She enters the command clearmake Makefiles to have imake create the appropriate Makefile in the architecture-specific subdirectory sun4. Note that the Makefile is a derived object, not a source file. Thus, there is no need to create an element from this file.

  4. She goes to the sun4 subdirectory, and then builds software for that architecture, using clearmake.

The sections below describe the way in which imake is involved in each of these steps.

Defining Architecture-Specific CPP Macros

Step #1 places the developer in an environment where the C preprocessor, cpp, defines one or more architecture-specific symbols. On an SunOS–4 host, cpp defines the symbols sun and sparc. This, in turn, causes imake to generate many architecture-specific (“machine-dependent”) cpp macros (See Figure 14-1):

Figure 14-1. Defining Architecture-Specific CPP Macros


Additional Sun-specific cpp macros are read in from the auxiliary file sun.cf.

Creating Makefiles in the Source and Build Directories

A file named Imakefile in the source directory is the imake input file. This file drives the creation of Makefiles both in the source directory itself, and in the architecture-specific subdirectories where software is actually built:

#ifndef InMachineDepSubdir
 .
 <code to generate Makefile in source directory>
 .
#else
 .
 <code to generate Makefile in an architecture-specific subdirectory>
 .
#endif

The Imakefile code used in the source directory simply defines a symbol to record the fact that builds will not take place in this directory:

#define IHaveMachineDepSubdirs

The resulting Makefile generated by imake includes a Makefiles target that populates an architecture-specific subdirectory with its own Makefile:

Makefiles::
    @echo "Making Makefiles in $(CURRENT_DIR)/$$CPU"
    -@if [ ! -d $$CPU ]; then \
        mkdir $$CPU; \
        chmod g+w $$CPU; \
    else exit 0; fi
    @$(IMAKE_CMD) -s $$CPU/Makefile \
    -DInMachineDepSubdir \
    -DTOPDIR=$(TOP) -DCURDIR=$(CURRENT_DIR)/$$CPU


Note: CPU environment variable determines name of architecture-specific subdirectory

The command clearmake Makefiles invokes imake once again, using the same Imakefile for input. This time, however, the symbol InMachineDepSubdir is defined, causing the actual build code to be generated.

The Imakefile in /proj/monet/src contains these macros:

OBJS = cmd.o main.o opt.o prs.o
LOCAL_LIBRARIES = ../../lib/libpub/libpub.a
MakeObjectFromSrc(cmd)
MakeObjectFromSrc(main)
MakeObjectFromSrc(opt)
MakeObjectFromSrc(prs)
ComplexProgramTarget(monet)

The resulting Makefile generated in the build directory, /proj/monet/src/sun4, includes this build script:

$(AOUT): $(OBJS) $(LOCAL_LIBRARIES)
    @echo "linking $@"
    -@if [ ! -w $@ ]; then $(RM) $@; else exit 0; fi
    $(CC) -o $@ $(OBJS) $(LOCAL_LIBRARIES) \
        $(LDFLAGS) $(EXTRA_LOAD_FLAGS)