Porting an Application to a New Operating System

 <  Day Day Up  >  

As previously discussed, porting is an activity of moving a custom application, written in one or more programming languages, to a new operating system (OS). This activity relates primarily to creating a new executable that is compatible with the new OS. This is accomplished by modifying the application programming interfaces (APIs) used by the software so that they conform to or match the APIs offered by the new OS while maintaining similar functionality.

A detailed understanding of the application logic is not necessary for this task. Instead, familiarity with the development tools and the linkers and library technology that exist for the target environment is required because the porting effort is primarily one of modifying, compiling, and linking the application to the new OS.

The first step in porting an application is to identify which APIs are incompatible with the new OS and need to be rebuilt. Once you flag an API as being incompatible, you must develop a solution for a compatibility library that you can use to replicate the functionality that existed on the old platform. You can then modify the source code of the application to use the new API, as defined in the compatibility library, wherever the older incompatible version occurs.

In the following sections, we describe how to create a build environment on the target system. In addition, we explore some of the issues associated with creating the new application executable and discuss nondestructive methods of transforming the code.

Creating a Target Build Environment

The development environment is composed of the development tools, the source code for the application, and any third-party products that are required to create the application. It also includes the hardware to support the developers who will use the tools to create the application executable and the supporting application infrastructure.

When migrating custom-written applications, you must also migrate the development environment to the new target environment. To migrate the build environment, perform the following tasks :

  • Prepare the hardware environments, including backup and restore facilities.

  • Identify the software to use in the new environment.

  • Acquire a recent reference build log.

  • Plan to acquire and install tools and utilities.

Prepare the Hardware Environments

The development environment is usually created on nonproduction hardware. Although the development hardware can be purchased with the production hardware, it will almost certainly be required before the production hardware. Arrangements must be made to ensure that the hardware used in the development environment is delivered in a timely manner. However, keep in mind that large production environments require significant installation and verification efforts. Because of this, they must be acquired long before they are actually scheduled for use. The vendor's sales representatives can assist you in determining the lead time required for the acquiring and installing both the development and production hardware environments.

Although the porting process can involve significant effort that uses numerous resources, the development environment should be sized to support steady-state development and maintenance rather than the loads created by the one-time-only migration effort. It might be possible to lease equipment to support the migration activity, but care must be taken to ensure that the following items are sufficient for your project:

  • Desktops are available for the migration engineers .

  • Licenses have been acquired for the software, where appropriate.

  • Compute capacity exists to support the migration effort.

  • Storage capacity exists to support the migration effort.

Identify the Software to Use in the New Environment

The target build software environment generally consists of the following items, which you should identify before beginning the porting exercise:

  • Development tools. The utilities and tools used to create application binaries in the new environment. Typically, these consist of things like the following items:

    • Compilers

    • Debuggers

    • Linker-loaders

    • Libraries

    • Preprocessors/precompilers

    These tools are frequently incorporated into an Integrated Development Environment (IDE) that might or might not support the sharing of software among a number of different development teams . The IDE allows developers to edit, compile, execute, and debug the application executable in an environment that supports tracing the execution of the application at the source level, inserts compiler errors into the source code in-line, and allows for recompilation at the click of a button.

    Custom-written applications usually use a utility to build or make the application executable. This utility will usually be controlled by a configuration file that defines the following items:

    • The tool used to process the source code file (for example, a C compiler, a C++ compiler, a FORTRAN compiler, or a C preprocessor)

    • The compiler options that must be used

    • The order in which libraries should be referenced for symbol resolution

    • The location at which to look for header files

    • The location at which to install the compiled binary

    The most popular version of this tool is the make utility. There are different variants of the tool with similar names and functionality (for example, nmake and gmake ). Another key tool used in the build environment is the source code repository that supports version control.

    These tools enable developers to create and retrieve snapshots of specific versions of the source code, allowing them to label a specific version of the code. For example, a release with specific features might be labeled REL_2.3. Later, when release 3.1 is in production, the developers might want to fix a bug in the 2.3 release of the product. Using the version control tool, they can retrieve a snapshot of the source as it was when the 2.3 release was created by requesting copies of all files as they were when they were labeled REL_2.3. These tools also allow developers to record a history of the changes that have been made to a file along with who made them.

    Source code version control systems usually require some training to use. The choice of which system to use is best left to the team that will support the development and maintenance of the application after it has been migrated . Common version control systems include the Source Code Control System (SCCS), Revision Control System (RCS), and Code Version System (CVS).

  • Application source code. Normally, the application source code will have been acquired before you begin the porting process so that it could be assessed when you estimated the level of effort required to implement the migration. During porting, the code must be transferred to the target environment and imported into the source code revision control system that the client will use in the new development environment.

  • Third-party products used to develop the application. These are additional libraries, classes, or templates that are not distributed with the OS or the development environment. They typically provide some functionality for the application in a supporting capacity or simplify the development of applications that rely on an object-oriented methodology.

Acquire a Recent Reference Build Log

The best way to ensure that you fully understand the build environment is to acquire a build log that was created when the application source that you are going to port was last compiled. There are two main reasons for acquiring a build log of the source that you are transforming:

  • The log will verify that the source builds without errors.

  • The log will capture the specifics of the build process, including the following:

    • Which libraries are used

    • Which tools are used

    • The order in which symbols are resolved

    • The switches and options used by the compiler and in the linking processes

    • The location of header files

Plan to Acquire and Install Tools and Utilities

For the purposes of creating the build environment, the build log is useful in that it specifies all the tools and utilities that are required to create the application executable. You will have to acquire new versions of these products that will run under the new target OS from the various software vendors . Lead times for acquiring products can vary, so some effort must be put into ensuring that the required hardware and software products are available before the porting effort is begun. Training might also be required for any new tools that are going to be used and should be arranged and delivered so that training does not impact the migration schedule.

When installing the new software, examine the make files and the build logs that were used or created on the old platform. Installing the new products in the same location under the new environment might reduce or minimize changes that have to be made to hard-coded paths in scripts or make files.

Access to documentation that describes the tools and APIs of the old environment is essential because the behavior of the old tools must be well understood . Although it is not a necessity, it can be useful to have access to the old environment if you need to verify how things were done on the old system.

Once the development environment is installed, create and compile several test examples to ensure that executables perform as required.

It is now time to install the source code control system and populate it with the source that is to be transformed. Mark or label the source in the repository to ensure that you can quickly go backwards if you need to compare a modified version of the source to the original code. In addition, ensure that you have tools like Perl, the sed commands, and shells to automate source code transformation.

Building a New Application for the Target Platform

Once you've verified that the development environment is functioning correctly, you can start to build the application. Ideally, the process used to build an application should be well documented. However, frequently, this is not the case. If build documentation does exist, it might not be current or reflect the latest changes that have been implemented to the code or to the build process. The advantage of having a reference build of the application is the understanding that you will be porting the application as it is configured in that reference snapshot of the code.

As mentioned previously, much of the information about how an application should be built can be obtained from the make files and the reference build log that were created when the application was built on the old system.

Building the application results in the creation of an application executable. The functionality provided by the application can depend on a number of different elements of the build process. The output of the build process usually depends on one or more of the following items:

  • Header files

  • Compiler options

  • Symbol resolution

  • Variable initialization

  • Conditional compilation

  • Precompilation

The following paragraphs explain possible dependencies that exist for the preceding items:

  • Header files. These are also referred to as include files because of the language construct used to make their contents available to the source code. Header files can exist anywhere in the file system. They usually provide definitions for both constants and types, as well as prototypes for library functions that are relevant to the OS. The header files provided by the system are usually located under the directory /usr/include and its subdirectories. Header files for third-party products can be placed anywhere, but you must tell the compiler where to look for them. Consult the old make files and build logs to determine which include file paths were specified in the old environment.

  • Compiler options. To build correctly, applications might require the use of specific compiler options. Although the compilation options will be readily available from the old make files and build logs, these options might not map directly to the new environment. You will have to consult documentation (man pages and manuals) from the old system to determine why these options were used. Once you have identified the required functionality, consult the documentation for the new compiler, determine the appropriate option to use, and modify the make files to use the new options. Note that some options might not be available in the new environment.

  • Symbol resolution. Multiple definitions might exist for a symbol within the code base of an application. Symbols are typically resolved against the first definition they encounter. By specifying which library to use, as well as the order in which libraries should be examined for a symbol, you can control which definition will be assigned a symbol. Examine the make file and build log to determine which libraries should be used and the order in which they should be examined.

  • Variable initialization. Variables can be initialized when the application executable is created. The make file and build log should specify whether any initialization is required and what values should be used. There are differences in how variables are initialized between compilers and languages. Ensure that the behavior is well understood and faithfully replicated. (For example, some compilers default uninitialized variables to 0, others use “32768, and still others use random numbers .)

  • Conditional compilation. Over time, the business functionality required from an application can change. New requirements will require changes to the application's source code base. When new functionality is needed for only a short period of time or in specific locations, different versions of the application will be required. Rather than creating a new copy of the existing source base and adding the new functionality to that code, developers typically add the new functionality to the same code base and make its inclusion dependent on an option that is passed to the compiler. For example, in the following conditional compilation code sample, function foo() will return 1 if BAR is defined; otherwise , the value is 0.

    Table 7-1. Conditional Compilation Code Sample
     foo() { #ifdef  BAR     return (1); #else     return (0); #endif } Fragment 7.1- Conditional Compilation 

    BAR is defined outside the code base when the executable is created, by specifying a switch to the compiler, using the syntax -DBAR . It will be difficult to figure this out without a copy of the old make file, build log, or accurate documentation.

    Using a single source base reduces the amount of maintenance that must be expended to keep all of the different versions up to date with new features or bug fixes.

    The knowledge of which switches to use during compilation must be provided by local domain experts from within a company.

  • Precompilation. In certain cases, the source code must be precompiled or preprocessed by a utility or scripts before the application is built. Unlike conditional compilation, preprocessing or precompilation results in a new source base that has been modified by the utility. This modified or preconfigured code is then used in the build process.

    Again, the knowledge of which options to pass to these precompilation utilities and the rationale for their use must come from the local domain experts within the organization.

Create a Compatibility Library

You should use a compatibility library to replicate the functionality of the old environment. Doing so will minimize the amount of change that will have to be made to the source code base. A compatibility library might be available from the vendor of the new system, or it can be created by the migration team that is transforming the code.

When migrating to the Solaris OS, consider using the tools and migration kits found at http://www.sun.com/migration. The following example shows how to create a function to minimize the change to an application to an application that was written for an HP-UX environment that is being ported to the Solaris environment.

Table 7-2. Compatibility Library Sample
 /*  *  This call is available on HP-UX 10.x (tm) only.  *  ltostr( )- converts long integers to strings  */ #include <errno.h> #include <stdlib.h> char *ltostr(long a,int base) {         static char buf[34];         char *theChar;         char sign=' ';         int tmp;         if(base > 32  base < 2) {                 errno=ERANGE;                 return NULL;         }         theChar=buf+sizeof(buf);         *theChar=0;         if(a < 0) {                 sign='-';                 a=labs(a);         }         do{                 tmp=a%base;                 *--theChar = tmp>9 ? tmp+'a'-10 : tmp+'0';                 a=a/b;         }while(a);         if(sign=='-')                 *--theChar=sign;         return theChar;         } 

APIs can vary among operating systems in a number of different ways:

  • The function name might not exist under the new application. The preceding example illustrates this situation. Before engineering a solution, determine whether the functionality exists under a different name .

  • The function might have a different prototype as well as a different functionality than it had in the original OS. These types of differences are rare and are becoming rarer. Over time, standards are minimizing the differences between UNIX implementations . Should the prototype and the functionality of an API differ between platforms, a new function will have to be created to provide the same functionality and footprint.

  • The function might have the same prototype as it had in the original OS, but different functionality. Again, this situation does not frequently arise. If only the return value is different, it might make sense to modify the code base of the application and use the function provided by the new system. If there are significant differences, a new version of the function, matching the function found on the old system, will have to be engineered.

  • Perhaps the most common case is that of the target OS having a different function prototype than that of the original OS, but similar functionality. There are three ways this can happen:

    • The type of the returned value is different than it was in the original OS. In this case, change the type to match that of the new OS, and change the source code for the application.

    • The number of arguments for the new OS is different than it was for the old OS. In this case, create a wrappered version of the function in the new OS.

    • The types of the arguments for the new OS are different than they were for the old OS. In this case, change the type of the variable in the source code to match that of the new OS.

Again, when attempting to port or transform code, attempt to minimize the changes that have to be made to the application logic, and use compatible functions whenever possible to minimize implementation errors.

Sun has created a set of compatibility libraries to assist developers port from some common operating systems. An example is the Solaris OE Implementation for HP-UX API. It implements a set of commonly used HP-UX functions for Solaris/SPARC ¢ or Solaris/x86. It is available at http://www.sun.com/migration/hp_ux/tools/hpuxapi.html. To use these libraries, simply download the file and unzip it in your directory. The following content will be created under the directory:

  • include : Contains all the header files for the library

  • src : Contains the compilation script compileapi.sh and all the source files for the library ( *.c )

  • lib : Contains the binary code of the library ( *.o ) and the archive file hp2sunmig.a

  • example : Contains examples of how to use and test the library on the Solaris 9 OE

  • test_plans : Contains the test-case documents

  • README : Overview of Solaris OE Implementation for HP-UX API contents; also contains the list of supported APIs

For a complete list of included functions, visit http://www.sun.com/migration/hp_ux/tools/emulation.html.

Modify the Make Environment

Complex applications can contain millions of lines of code. Well-written applications are usually implemented as a number of modules that are broken down along functional lines. Each module is usually contained in its own subdirectory. The code in each subdirectory might be dependent on another subdirectory within the application, as well as on libraries or header files provided by the system.

Building the application should be a monolithic activity that is performed at a high level within the source tree. Rather than starting at the bottom of the tree (the leaf nodes) and building each component from the bottom up, the structure of the build environment should be such that the application can be built from the root node of the source code tree.

Although a little more work will have to go into coordinating the make files at a lower level with those higher up in the tree, the effort will be worthwhile because the location of include files, compiler options, and so forth can be assigned at one higher level and inherited through the use of include directives by those at a lower level in the source hierarchy. This minimizes the opportunity for errors to creep into the build process, because all definitions are created at a high level.

Understand the Application Configuration

Applications frequently require configuration information. They might read this data from a configuration file, or they might obtain it dynamically from the environment by using a getenv() call. In addition, configuration files might contain data that is environmentally dependent, meaning that it will have to change when you change platforms or operating systems. Be sure to review the data within the configuration file with experts on the current environment who are knowledgeable about the application on the new environment. For example, Oracle configuration options differ depending on whether you implement your solution on the Solaris OS or HP/UX. Attempting to determine the correct configuration data for an application requires a detailed understanding of the application logic, which the porting team might not have. Remember, relying on individuals who are knowledgeable in the application and its configuration ensures the most productive use of resources.

Deciding Whether to Support Backward Compatibility

When modifying the code base for an application during a migration, you must decide whether the new code base will be able to function in the old environment. This is known as backward compatibility. After the migration, the same source code could be used in the old build environment to produce an application executable targeted for the old platform, or the same source base could be used in the new build environment to support the new platform.

In the source code base, backward compatibility is accomplished by nondestructive source code modification, usually implemented through the use of conditional compilation, as illustrated in the following code fragment. _sun is predefined by the Solaris compiler.

Table 7-3. Backward Compatibility Example
 #ifdef _sun     newFunction() #else     originalFunction() #endif 

Backward compatibility within a source code base can reduce the amount of work that has to be done by the development staff. If a single source code base is created that can support both the old and the new environments, changes resulting from new requirements or changing business logic only have to be implemented once in the source code.

While code that is heavily modified to include conditional compilation directives can be hard to read and maintain, the benefits of using conditional compilation directives far outweigh the cost. In fact, migrating to the Solaris OS does not usually require significant code modification.

 <  Day Day Up  >  


Migrating to the Solaris Operating System
Migrating to the Solaris Operating System: The Discipline of UNIX-to-UNIX Migrations
ISBN: 0131502638
EAN: 2147483647
Year: 2003
Pages: 70

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net