Portability

Chapter 4 - CPAN Module Distributions
by?Sam Tregar?
Apress ? 2002
has companion web siteCompanion Web Site

Perl is perhaps the most portable programming language ever created. Perl modules can be written to work on virtually every modern computer system.[21] Similarly, Perl modules can also be written to work with a range of versions of Perl. In general, this is accomplished by limiting your use of certain Perl features-be they features that work differently on different operating systems or features that are broken or nonexistent in older versions of Perl. This section will give you information on the most commonly seen portability problems in CPAN modules; for an exhaustive list, take a look at the perlport document that comes with Perl.

Operating System Independence

Writing operating system-independent code requires you to know which features might behave differently when used on different operating systems. This topic alone would be enough to fill a book at least as large as this one, but next you'll find information on the most commonly encountered portability problems.

Line Endings

Historically operating systems have had different ideas about what to put at the end of a line in a text file. UNIX systems use a single byte-\012 or LF. MacOS also uses a single byte-\015 or CR. Microsoft's operating systems (DOS and Windows) use 2 bytes-\015\012 or CRLF. However, in Microsoft's stdio[22] implementation, CRLF is translated into LF on input and back into CRLF on output, but only when reading or writing text files. As a result, in memory the lines have UNIX line endings but on disk they have the distinctive 2-byte format.

Perl provides the \n escape code for strings and regular expressions that matches what the platform thinks of as a line-ending-be it CR or LF-so you usually don't need to think about it when reading and writing text files. However, the issue has implications for the handling of binary data. Consider the following CGI code that reads in a binary image file from disk, smoothes it, and prints it to STDOUT:

 open(IMAGE, "image.jpg") or die $!; # open the image file my $image = join(",<IMAGE>);   # read the image data into $image smooth($image);                       # smooth the image print STDOUT $image;                  # print out the image to the client 

This code will work fine under UNIX and MacOS, but may result in a corrupted image under Windows. The reason is that by default Windows handles files in text mode. That means translating all CRLFs into CRs on input and the reverse on output. The problem is that since image.jpg is a binary file, it might well have CRLF sequences that aren't meant to be line endings. To safely handle binary data, you need to use the binmode() function:

 open(IMAGE, "image.jpg") or die $!; # open the image file binmode(IMAGE);                       # the image file is binary data my $image = join(",<IMAGE>);   # read the image data into $image smooth($image);                       # smooth the image binmode(STDOUT);                      # about to print binary data on STDOUT print STDOUT $image;                  # print out the image to the client 

Notice that in this case binmode() is necessary on both IMAGE and STDOUT to avoid corrupting text-mode translations.

Another place where line endings rear their ugly heads is in network programming. Many network protocols state that lines must end in CRLF. The only way to be sure that you're ending your lines properly is to explicitly add the line-ending bytes:

 print SOCK "GET / HTTP/1.1\015\012"; 

In particular, using \r\n will not work since the setting for \nvaries from system to system.

File Systems

Nothing varies as much between operating systems as file system usage. Everything is up for grabs-the path separator, maximum size of filenames, characters allowed in filenames, the number of "root" directories or "volumes", the way network resources are named in the file system, and so on. Listing out all the variations would only serve to convince you that producing portable code that accesses the file system is impossible. Instead I'll just cut to the good news-it is possible and it's not very hard at all.

Perl comes with a module called File::Spec[23] that provides functions for manipulating filenames and paths. To get a list of available methods, read the documentation for the File::Spec::Unix module-all the available subclasses support the same methods.

The only aspect of file naming that File::Spec doesn't treat is the constraints on the actual names themselves. The rule here is keep them short and simple. To be maximally portable, your filenames shouldn't be longer than 11 characters and should contain only one dot (.). Also, you can't count on a case-sensitive file system, so having both test.pl and TEST.pl in the same directory is out. Spaces in filenames are generally verboten, as are punctuation characters aside from underscores and hyphens.

Portable modules should never contain hard-coded paths. If you need to refer to an external file, you should provide a way for users of the module to specify it. Of course, when they do, they'll be using the filenaming conventions of their platforms, so use of File::Spec is necessary.

On Self-Reliance

Think of a user's system as a strange and alien land-you don't know where anything is, you don't know who to ask for help, and you don't speak the language well enough to understand the user's reply if you did. In Perl terms, don't expect to be able to call external programs. Even if you could reliably locate the program you want, there's probably a version out there that produces output you aren't expecting. A classic example is the ps utility found on many UNIX systems. Every ps accomplishes the same basic task: displaying information about running processes. Unfortunately, every flavor of ps is different-each takes different options and produces different output formats.

If you need a particular service in a portable module, you have two options- code it yourself in pure Perl, or find a module that has it. The latter solution is definitely preferable-portability problems are common ground shared by the entire CPAN community. The chances are quite good that the service you need is already available on CPAN.

Coding Around the Problem

One way to address portability is to build it into your module's design. A good example of this is in the way the File::Spec module works. File::Spec provides a standard API across all platforms using an object-oriented inheritance strategy. File::Spec is conceptually a virtual base class from which the concrete File::Spec subclasses inherit.[24] When you perform a use File::Spec, code inside File::Spec determines which OS you're running under[25] and loads the appropriate File::Spec subclass, be it File::Spec::Unix or File::Spec::OS2. This pattern, breaking out OS-specific implementations of a standard API, is an excellent one for portable code. One major advantage is that it allows the code to start small-supporting only a few systems-and grow in portability as additional subclasses are added.

Be Explicit

It's good to be portable; but if you can't, then you should be explicit about it. One way to do this is to put code in your Makefile.PL that explicitly checks the OS at installation. For example, if you know that your module can't possibly work under Microsoft Windows, you could use code like this:

 if ($^O eq 'MSWin32') {     die "Sorry, the Data::Counter module doesn't support Windows yet!\n"; } 

This is much nicer than allowing a user to install your module and realize that it doesn't work later. That said, this technique is hard to get right since you have to either know all the $^O strings where your module will work or all the $^O strings where it won't work. Neither piece of knowledge is easy to come by considering the large number of possibilities.

Perl Version Independence

Another type of portability is compatibility with older versions of Perl. There are many reasons why people don't upgrade their Perl, some good and some bad. Ignore older versions of Perl at your own peril-you never know when you'll end up in a job where the hoary old sys admin insists that Perl 5.004 is as good today as the day it was born!

Supporting older versions of Perl means avoiding new Perl features like the plague. In particular, you'll need to watch out for using the newer syntactic features of modern Perl-our, use warnings, v-strings (numbers of the form 1.2.3), unquoted hash strings, unquoted strings on the left-hand side of a =>, new-fangled regex extensions, and much more. Some of these will merely cause warnings under old versions of Perl, but many will break the module unequivocally.

Sometimes it's possible to take advantage of newer features while maintaining backwards compatibility. For example, if your module is intended to work with 5.003, then you'll need to avoid using File::Spec[26] since it wasn't included with Perl until 5.004_05. On the other hand, it's hard to support MacOS and VMS without using File::Spec. To solve this problem, you might conditionally include File::Spec if the version of Perl you're using is high enough. For example:

 if ($] > 5.00405) {    require File::Spec;    $path = File::Spec->catfile($foo, $bar, $baz); } else {    $path = join('/', $foo, $bar, $baz); } 

The key to maintaining compatibility with older versions of Perl is testing. Perl has changed greatly over the years, and it's not easy to keep up with all those changes. If you're serious about supporting older versions of Perl, then you'll need to keep versions around to test your module against. It's either that or release it, and sit back and wait for the bug reports!

[21]With the notable exception of small-scale embedded systems too small for Perl 5. Perl 6 aims to address this deficiency. See the perlport documentation for a full list of currently supported systems.

[22]The system library used by Perl to do file I/O

[23]This module comes with Perl and was written by Kenneth Albanowski, Andy Dougherty, Andreas Köenig, Tim Bunce, Charles Bailey, Ilya Zakharevich, Paul Schinder, Shigio Yamaguchi, and Barrie Slaymaker.

[24]Notice that I said "conceptually." The implementation is actually the reverse-File::Spec dynamically chooses one of the "subclasses" as its parent class. Perl OO is fantastic!

[25]Using the $^O variable, which carries a short identifier specifying the OS. See the perlport documentation for a complete list.

[26]Okay, you could put it in your PREREQ_PM setting in Makefile.PL, but let's imagine you don't want to do that because you're a kind and giving person who believes in playing along with an author's examples.



Writing Perl Modules for CPAN
Writing Perl Modules for CPAN
ISBN: 159059018X
EAN: 2147483647
Year: 2002
Pages: 110
Authors: Sam Tregar

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