package Versioner; use strict; use Carp; require Exporter; use vars qw(%is_loaded $VERSION); $VERSION = 0.07; sub decide_version { my $class = shift; my $ver = $ENV{IS_PROD} ? "Prod" : "Devel"; return "${ver}::$class"; } # This should not be inherited sub Versioner::private::get_implementation { my $class = shift; my $file = $class; $file =~ s/::/\//g; $file .= ".pvm"; foreach my $dir (@INC) { if (-e "$dir/$file") { $file = "$dir/$file"; local $/; local *FH; open (FH, $file) or confess("Cannot load '$file' for '$class': $!"); return qq( package $class; \n#line 1 "{load '$file' for $class}"\n) . ; } } confess( "Cannot find $file to implement $class in \@INC\n" . " (\@INC contains: @INC)\n" ); } sub import { my $class = shift; return if $class eq 'Versioner'; # I only version others! my $load = UNIVERSAL::can($class, "decide_version") or croak("Class $class does not inherit from Versioner"); my $implement = $load->($class); unless ($is_loaded{$implement}) { # Oops, need to load eval Versioner::private::get_implementation($implement); if ($@) { croak("Cannot implement $class with $implement: $@"); } no strict 'refs'; push @{"${implement}::ISA"}, "Exporter"; $is_loaded{$implement}++; } # Inherit? no strict 'refs'; unless (grep {$implement eq $_} @{"${class}::ISA"}) { @{"${class}::ISA"} = $class->inherit_version($implement); } # And transparently replace this with the right import my $meth = UNIVERSAL::can($implement, "import"); unshift @_, $implement; goto &$meth; } sub inherit_version { return (__PACKAGE__, $_[1]); } 1; __END__ =head1 NAME Versioner - Implements an import method for versioned modules. =head1 SYNOPSIS Write a module Some/Module.pm: package Some::Module; use Versioner; @ISA = qw(Versioner); 1; # End of module Place your implementations in C<$base/Prod/Some/Module.pvm> and C<$base/Devel/Some/Module.pvm> where C<$base> is any directory in C<@INC>. The environment variable C determines which version you will run. =head1 DESCRIPTION The Versioner module implements a default C method that causes a base module to be implemented by the appropriate version of the module. Which version is chosen is determined by the C method. The default implementation looks at the environment variable C and puts "Prod::" or "Devel::" in front of the package name as appropriate. Once the package is known, the implementation is loaded if has not been already, inheritance is set up if it is needed, and then the C method of that version is called. This will default to the C method from L. If the module needs to be loaded, first the package name of the implementation is turned into a file-name by converting "::" to "/" and putting .pvm (Perl Versioned Module) at the end. Then C<@INC> is searched for the implementation. If it is found it is compiled with L on in the correct package, and then made to inherit from L. Modules that don't want L are free to unimport it with C. Here are the methods that can be overridden =over 5 =item B This method takes the class being versioned and returns the class that should implement it. The default implementation prepends "Prod::" or "Devel::" depending on the value of C<$ENV{IS_PROD}>. This is convenient for people who develop in a development branch and regularly cut that over to production. Should you set up a custom version, carefully consider how you will manage it. Mixing multiple versions of the same module is a considerable increase in complexity, so pick a scheme that you are confident you can manage. =item B This method takes the class being versioned and the version chosen, and returns what I<@ISA> should be for that class. By default this returns C<("Versioner", $implementation)>. =head2 Applicability of L Aside from the detail that the implementation should not declare its own package or load and inherit from L, this loading mechanism should be totally transparent. You can even define your own custom C methods and they will not see the loading. But if you do that you will probably want to call C with: __PACKAGE__->export_to_level($level, @what); so that you can avoid specifying the package anywhere in the implementation. You should be able to convert the versioned implementation into an equivalent module by adding the right package declaration, putting in C, and appending C to C<@ISA>. (Declaring it if need be.) The only catch should be that the versioned module will inherit C and C from this module. =back =head1 BUGS L has a number of limitations. The primary one is that it will not work properly if the implementations declare their own package, nor can it easily test for that. Secondly it can only version pure Perl modules. You are on your own for xsubs. A third problem is that while you can load a different implementation over an old one, it does not try to unload the previous one. Finally loading a different implementation over an old can cause warnings. The following custom import method will solve that: sub import { local $^W; __PACKAGE__->export_to_level(1, @_); } However using this can slow the loading process, particuarly for OO modules that didn't plan to import anything. Also no easy way presents itself to locally turn warnings off while having the versioning completely transparent to the implementation. =head1 AUTHOR INFORMATION Copyright 2000 by Ben Tilly. All rights reserved. This module is free software; you can redistribute it under the same terms as Perl itself. Address bug reports and comments to ben_tilly@hotmail.com. =cut