http://qs321.pair.com?node_id=156419

RhetTbull has asked for the wisdom of the Perl Monks concerning the following question:

My problem

I have a subroutine that is used by my program but whose implementation depends on information not known until runtime (e.g. which OS is the program running under). The subroutine depends on some modules which depend on the OS. E.g. if I'm running under Windows, I use Win32::some_module and my sub uses that module but if I'm on linux I use some::other::module and the sub would use routines from that. I wrap this all in a sub to hide the gory details from the main script.

My solution

I use eval/require/import to get the right modules and define the sub accordingly at runtime. For example
#!/usr/bin/perl use strict; use warnings; if ($^O eq 'MSWin32') { eval q( require module1; import module1; sub bar { print "Win32\n"; } ); } else { eval q( require module2; import module2; sub bar {print "not Win32\n"; } ); } #later in the program... #we don't care what OS is running -- implementation details are hidden bar();

My question

This works fine. What I want to know, does anyone have any better ideas for implementing this sort of thing? Is there a more elegant solution? Are there any potential pitfalls with the solution I'm using? Of course, all of this could be wrapped in a package to make it more transparent to the calling script but I'm interested to know how others have handled this sort of thing.

--RT

Replies are listed 'Best First'.
Re: Runtime module use and sub definitions
by chromatic (Archbishop) on Apr 03, 2002 at 20:48 UTC
    If you can find a different way to initalize your sub, you can get rid of the string eval, which is my only objection.
    eval { require module1; module1->import(); } *bar = sub { print "Win32\n" };
    You may need a forward declaration, as this is untested. Otherwise, that's probably the first thing I'd try. (It's simple.)
      I like that much better than the string eval. I tried this and it seems to work fine:
      #!/usr/bin/perl use strict; use warnings; if ($^O eq 'MSWin32') { eval { require Win32::OLE; Win32::OLE->import; }; *bar = sub { print "Win32\n" }; } else { *bar = sub { print "Not Win32\n" }; } #later in the program... #we don't care what OS is running -- implementation details are hidden bar();
        You can leave out that eval completely, unless you intend to check for errors during the require and import statements.
Re: Runtime module use and sub definitions
by CukiMnstr (Deacon) on Apr 03, 2002 at 21:32 UTC
    you could also use an init sub, where you require the modules you need, and set some variables to the different subs...

    note: I have not tested this code, and I haven't done any cross-platform scripting that requires something like this, I just remembered seeing this technique in Perl For System Administration by David N. Blank-Edelman (ORA).

    sub Init { use Common::Module; my ($common_var01, $common_var02) = ($foo, $bar); if ($^0 eq "MSWin32") { require Win32::Baz; require Win32::Quux; common_sub01 = "common_sub01_nt"; common_sub02 = "common_sub02_nt"; } else { require Foo; common_sub01 = "common_sub01_unix"; common_sub02 = "common_sub02_unix"; } } sub common_sub01_nt { ... } sub common_sub01_unix { ... } # main program # (where we call the subs) &$common_sub01($foo, $bar) if $foobar;

    hope it helps,

Re: Runtime module use and sub definitions
by Desdinova (Friar) on Apr 03, 2002 at 21:23 UTC
    __Rant mode on__
    This node touches one of my current sore spots. One of the main reasons that I use PERL is because I work in an enviroment with a good mix of Windows and several flavors of (li|u)nix, and in many cases I can move my scripts from one platform to another without any major modifications. Where i most often run into trouble with this is with modules that only work on Win32 or Unix. While i can see this happening where the module is limited to a specific platfrom operation (ie updating the passwd file on unix) but there are some that it makes no sense where there should not be any platform dependence yet the module is linked ot a specific platform. I just wish those who write modules would conssider at least testing thier modules who take the time to wrtie so they can be portable to other platforms whenever possible.
    __Rant Mode Off __

    Now that I got that out of my system, thanks for posting this snippet, i can no have another tool to deal with those situations.
      I'm sure that the authors of these modules you're finding which can be easilly ported to other platforms would appreciate a patch from you. Not everyone has easy access to lots of development platforms.
Re: Runtime module use and sub definitions
by derby (Abbot) on Apr 03, 2002 at 20:47 UTC
    Well, not too much more elegant but you could use autouse

    #!/usr/bin/perl # # Bad code - will not work # use strict; use warnings; if ($^O eq 'MSWin32') { use autouse 'MyMod_W32' => qw( bar ); } else { use autouse 'MyMod' => qw( bar ); } bar();

    update: Well that's what happens when you post untested code! This will not work. Duh. The 'use' happens at compile time. The last bar defined is the first one used.

    -derby

Re: Runtime module use and sub definitions
by gregorovius (Friar) on Apr 04, 2002 at 16:53 UTC
    An object oriented approach to this problem would be to use a Factory class to instantiate the right object depending on what OS you run your script on (this example will run as is, but in real life you probably want to separate the modules into different files and "require" them in from the factory class as needed, as you'll surely not have your Windows specific CPAN modules on your Unix boxes and vice-versa):
    package FooFactory; use strict; sub get_foo { if ($^O eq 'MSWin32') { return Win32Foo->new(); } else { return UnixFoo->new(); } } #================= package Foo; use strict; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); return $self; } sub bar { die "abstract, must override"; } #================= package Win32Foo; use strict; use vars qw(@ISA); @ISA=qw(Foo); #use Win32::OLE; #Win32::OLE->import; sub bar { print "Win32\n"; } #================= package UnixFoo; use strict; use vars qw(@ISA); @ISA=qw(Foo); sub bar { print "UNIX\n"; } #================= # USAGE: my $foo = FooFactory->get_foo(); $foo->bar();