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

I set up a simple UNIVERSAL::VERSION method that allows me to search for an alternate versioned module ( e.g., one under development) instead of bailing out if the version number is too high. This works but not as cleanly as I'd like: I pull in the found module and the original. Is there any clean way to stop compilation of a given module without affecting the rest of the compile/run?

Related to this, I considered writing a true no by implementing a UNIVERSAL::unimport method that "unpollutes" the using module's namespace by removing aliased symbol table references. But it seems you can't undefine these. I tried code like this:
undef *{"${caller}::{$sym}"}{CODE};
but that gives me the error:

Can't modify glob elem in undef operator

I saw code in the Monastary that claimed to do it like this:
undef &{*{"${caller}::{$sym}"}{CODE}};
But this doesn't seem correct to me -- they're undefining the function, not the symbol table reference to it, right? That seems supported by the different error I get:

Unable to create sub named ""

A note in Advanced Perl Programming seems to indicate that you can't undo aliased symbol table names -- only use local so they revert back to their old values automagically. But I can't force a local here since the no is an optional, separate piece from the use .

Any ideas? I haven't found anything yet I can't do somehow in Perl, but this one has me stumped.

Replies are listed 'Best First'.
Re: version based compilation
by robin (Chaplain) on Dec 29, 2001 at 20:32 UTC
    Interesting question! If I've understood correctly, you want to remove a subroutine alias, without actually undefining the subroutine itself. The only way I can think of to do that is:
    # Usage: unalias ($package, $name) # Will remove the subroutine entry from the package's glob sub unalias { my ($package, $name) = @_; no strict; # Disable the safety systems local *temp; # Make a local glob my $glob = ${"${package}::"}{$name}; # Grab the glob *temp = *$glob{SCALAR}; # Copy the scalar part *temp = *$glob{ARRAY} if defined *$glob{ARRAY}; *temp = *$glob{HASH} if defined *$glob{HASH}; *temp = *$glob{IO} if defined *$glob{IO}; *temp = *$glob{FORMAT} if defined *$glob{FORMAT}; ${"${package}::"}{$name} = *temp; }
    That seems to do what you asked for. So you can use File::Copy, and then at some point unalias('main', 'copy'); and the alias is removed. Is that what you meant?

      Very cool. That works. Assign everything but the piece you don't want. That's one I didn't try. And it's so obvious once you see it.

      Since you solved that one, how about the bigger question that started me down this path: When a module's VERSION method is called, is there a way to stop compilation of the module at that point and switch to another one? This all started with an experimental piece of code like this:

      use Carp; sub UNIVERSAL::VERSION { my $class = shift; my $wanted_version = shift; my $fixed_version = $wanted_version; my $caller = caller(); my $version_class; $fixed_version =~ s/\./_/g; $version_class = "${class}_${fixed_version}"; eval <<"END_EVAL"; package $caller; no $class; require $version_class; $version_class->import(); END_EVAL croak "Version $wanted_version of $class not found ($version_class +): $@" if ($@); }

      I believe this works because the eval code runs after the BEGIN code (from use) that invoked the VERSION method. So the versioned copy of the module loads after, overwriting aliases. But ideally I'd want to just stop compilation of the non-versioned module so there are no BEGIN, CHECK, END type blocks pulled in.

      Thanks for the help on the one above. Most people I start talking typeglobs and symbol tables to just glaze over.

        Sadly, it's impossible to stop compilation of the module from a VERSION sub, because the module has already been compiled before the VERSION sub is invoked! You can check that by writing a BEGIN block which prints something -- you'll see that the BEGIN block is called before the VERSION sub.

        If you think about it, it has to be done that way. The module needs to be compiled first, otherwise the import() and VERSION() subs wouldn't be defined. Even though CHECK blocks are called later, it's still too late to stop them either: when a CHECK block is compiled, it's added to an internal list of routines to call at the end of compile time, and there's no way to modify that list without writing some hairy C code.

        If you have my Want module installed, you can stop the module use process dead from within the VERSION method: There's an undocumented internal routine Want::double_return which diddles perl's internal data structures so that when you return from the sub you're in, the sub that called you immediately returns as well. So if your VERSION routine contains:

        use Want (); Want::double_return(); return;
        then the use process will be stopped right there, and the import routine will never be called. Unfortunately that still won't stop BEGIN or CHECK blocks, and anyway it's rather too much like black magic for everyday use. Fun though :-)
Re: version based compilation
by chromatic (Archbishop) on Dec 29, 2001 at 23:56 UTC
    Could you do something with Module::InstalledVersion? If you were to localize and shift around parts of @INC, you might be able to find different versions. If you have a rough idea of the path, you could strip out the regex bits and use them yourself.

    That's not a bad module idea, requiring a certain version or version range of a module, provided you have several installed. Anyone want to write it, or did I just volunteer?

      Maybe. I already have something that lets me look in alternate @INC paths for modules under development. This one was intended to allow me to not have to worry about copying that full directory hierarchy (most of our modules hang in a 3 deep path). Instead I wanted to just copy files in the same working directory and have development test code specify the next highest version. I'd test like that, then copy the new version of the file to the "current" one when done, saving the old one under an old version (at least for a while) Anything other than the current version would be file named by using a version suffix. That would allow for a pretty unlimited versioning development path (I think) without requiring new include paths. I'm probably missing other pieces though -- I just set up that small bit of test code so far.

      The current module (one with the normal file name) is thus acting as the master. If someone asks to use a version other than the one it is, it would go out and try to substitute that version. That's where I'd like to stop compilation of the master file and switch over to the alternate version instead. All the unaliasing tests (useful in their own right) were the result of not being able to stop compilation of the master while continuing compilation of the alternate. Instead I let the master complete, I unalias all its imports, then compile the alternate version. As mentioned, this won't handle changes to BEGIN, END, CHECK style blocks across versions.

      Maybe hanging this off the call to the VERSION method has led me down the wrong path here though.

      More ideas welcome ...

Re (tilly) 1: version based compilation
by tilly (Archbishop) on Jan 05, 2002 at 23:34 UTC
    I did a proof of concept module quite a while ago for a problem which is very similar to this at Versioned modules. I never went anywhere with it, nor do I want to. But if you want to pick up where I left on and run with it, go right ahead.