[Neworleans-pm] Fwd: Perl 'Expert' Quiz-of-the-Week #24 (Module dependency evaluation)

E. Strade, B.D. estrabd at yahoo.com
Thu Sep 23 08:47:05 CDT 2004




=====
http://www.brettsbsd.net/~estrabd

__________________________________
Do you Yahoo!?
Yahoo! SiteBuilder - Free, easy-to-use web site design software
http://sitebuilder.yahoo.com

----- Original message -----
From: "Randy W. Sims" <randys at thepierianspring.org>
To: perl-qotw at plover.com
Date: Thu, 23 Sep 2004 05:54:34 -0400
Subject: Perl 'Expert' Quiz-of-the-Week #24 (Module dependency
evaluation)


IMPORTANT: Please do not post solutions, hints, or other spoilers
        until at least 60 hours after the date of this message.
        Thanks.

IMPORTANTE: Por favor, no enviéis soluciones, pistas, o cualquier otra
        cosa que pueda echar a perder la resolución del problema hasta
        que hayan pasado por lo menos 60 horas desde el envío de este
        mensaje. Gracias.

WICHTIG: Bitte schicken Sie keine Lösungen, Tipps oder Hinweise für
        diese Aufgabe vor Ablauf von 60 Stunden nach dem Datum dieser
        Mail. Danke.

BELANGRIJK: Stuur aub geen oplossingen, hints of andere tips in de
        eerste 60 uur na het verzendingstijdstip van dit
        bericht. Waarvoor dank.

Qing3 Zhu4Yi4: Qing3 Ning2 Deng3Dao4 Jie1Dao4 Ben3 Xin4Xi2 Zhi1Hou4 60
        Xiao3Shi2, Zai4 Fa1Biao3 Jie3Da2, Ti2Shi4, Huo4 Qi2Ta1 Hui4
        Xie4Lou4 Da2An4 De5 Jian4Yi4.  Xie4Xie4.

----------------------------------------------------------------

If you've ever written any modules then you are probably familiar with
the concept of requirements or prerequisites. These are dependencies
that must be met in order for your module to work correctly. For
example, if your module uses another module then that module must be
present on the user's system in order for your module to function
correctly.

If you've used MakeMaker then you've probably written something like:

     use ExtUtils::MakeMaker;

     WriteMakefile(
         NAME          => 'Your::Module',
         VERSION_FROM  => 'lib/Your/Module.pm',
         PREREQ_PM     => {
             'Test::More' => 0,
             'File::Spec' => 0.82,
         },
     );

or with Module::Build:

     use Module::Build;

     my $build = Module::Build->new(
         module_name => 'Your::Module',
         license => 'perl',
         requires => {
             'File::Spec' => 0.82,
         },
         build_requires => {
             'Test::More' => 0,
         ),
     );

     $build->create_build_script;

Both of the above say tell their respective build tools that File::Spec
version 0.82 or above and any version of Test::More are required.
Module::Build is a little more flexible in that it lets you indicate
that a module is required only during the build phase (build_requires)
or that a module is recommended but not required (recommends). Further,
Module::Build lets you be more specific about versions, using comparison
operators. For example:

     requires => {
         'Some::Module' => '>= 0.7, != 1.0, < 2.0',
     }

Says that a version of Some::Module of 0.7 or greater, excluding version
1.0 and less than version 2.0 is required.

However, even with Module::Build's greater flexibility there are a lot
of requirements that are still not possible to describe. And some
modules go to great lengths to do this dynamically. The problem with
this is that it makes it more difficult for tools like CPAN.pm,
CPANPLUS, and some automated tools to take advantage of without running
the Makefile.PL or Build.PL file. Ideally, it would be nice to specify
complex requirements in the now standard META.yml file, which contains
meta data about a distribution in YAML format (http://yaml.org/). Then
we could have a standard module that can read those requirements and
validate them. This module could be used by Module::Build,
ExtUtils::MakeMaker, CPAN.pm, CPANPLUS, and any other tool that needs to
validate requirements.

The types of things we would like to handle in the requirements
specification are boolean expressions && (and), || (or), and ^^ (xor);
grouping with parenthesis; and macro definition and expansion.

An example of boolean expressions (suggested by a discussion with David
Wheeler and Ken Williams on the module-build list) would be:

     requires => q[
         (DBD::Pg && DateTime::Format::Pg)
           ||
         (DBD::mysql && DateTime::Format::mysql)
     ]

This says that we need any version of either of these two sets of
modules. If we need to, we can also include version specifications:

     requires => q[
         ( DBD::Pg > 1.1 && DateTime::Format::Pg )
           ||
         ( DBD::mysql <= 1.2 && DateTime::Format::mysql )
     ]

Note that when a branch of the 'or' expression evaluates to true, it is
not neccessary to evaluate the remaining branches - short-circuit
evaluation. However, all branches of an 'and' or 'xor' expression must
be evaluated for correct error reporting.

Of course we also want to remain compatible as much as possible with the
old specifications.

For the macros, we're mostly interested in the use of predefined macros.
For example:

     requires => q[
         ( Term::Readline::Gnu )
           ||
         ( {OSNAME} == MSWin32 && Term::Readline::Perl )
     ]

(A possible extension would be to add a set operator (in) for versions
and any other values such as '{OSNAME} in [VMS MSWin32]' or
'Some::Module in [1.0..1.9 !1.7]'.)

Other useful macros might be {MULTITHREADED}, {LARGEFILES}, etc.

Finally, it can be useful to allow definition of macros to simplify
expressions. For Example:

     requires => q[
         def Pg = DBD::Pg && DateTime::Format::Pg;
         def mysql = DBD::mysql && DateTime::Format::mysql;

         {Pg} || {mysql}
     ]

Feel free to experiment with different syntax. The only hard
requirements are that it supports: boolean expressions, grouping, and
predefined macros. For example, my original suggestion to Ken Williams,
Module::Build's author, was something of the form:

     requires => {
         'db_driver'    => q[ {postgresql} || {mysql} ],
         '{postgresql}' => {
             'DBD::Pg'                 => 0,
             'DateTime::Format::Pg'    => 0,
         },
         '{mysql}'      => {
             'DBD::mysql'              => 0,
             'DateTime::Format::mysql' => 0,
         }

This syntax makes parsing a little simpler, but otherwise allows the 
same features. The keys with braces around the names are not evaluated; 
They are definitions of macros that are only evaluated when they appear 
in the value of another key.

This weeks quiz is to write a module (Prereq::Expr) that can take a 
specification of the type described above, and evaluate it to determine 
if the requirements are satisfied. The specification is the value 
assigned to the 'requires' key in the examples above. 
(Prereq::Expr->eval( $dist{requires} )). It can be either a hash, 
string, or array, whichever makes more sense. But, it should be able to 
handle the old style requirements.

In order to determine the version of an installed module, I've extracted
the routines from Module::Build and put them in a module at:

        http://perl.plover.com/qotw/misc/e024/Versions.pm

Use it like this:

        use Versions;
        my $version = Versions->from_module('File::Spec');

or

        my $version = Versions->from_file('/path/to/module.pm');

To simplify comparisons, you can assume that versions are real numbers
and just compare with perl's built-in numeric comparison operators. For
the purpose of this quiz, it's not necessary to worry about alpha
versions and all the complications of comparing versions. If you are
interested, you might check out John Peacock's version and
version::alphabeta modules.

One of the more interesting problems with this quiz, I think, is the
problem of reporting missing requirements. When you can have arbitrarily
large sets of alternative modules, it's not obvious (to me) how best to
notify the user in an orderly and comprehensible way.

I do, of course, have an ulterior motive in this quiz. This form of
complex requirements has been on the Module::Build TODO list for a long
time. I'd like to submit the solutions posted, with the permission of
their respective authors, to Ken Williams for possible inclusion in some
form or another in a future version of Module::Build.

If you want to implement this in a language other than perl, you can
simply replace Versions.pm with a module/class or whatever that simply
contains a list of hardcoded versions or whatever make sense for your
language of choice. The important part is the expression parsing and
evaluation.




More information about the NewOrleans-pm mailing list