<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=US-ASCII">
<META NAME="Generator" CONTENT="MS Exchange Server version 5.5.2657.73">
<TITLE>Perl 'Expert' Quiz-of-the-Week #24 (Module dependency evaluation)</TITLE>
</HEAD>
<BODY>

<P><FONT SIZE=2>IMPORTANT: Please do not post solutions, hints, or other spoilers</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; until at least 60 hours after the date of this message.</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thanks.</FONT>
</P>

<P><FONT SIZE=2>IMPORTANTE: Por favor, no enviis soluciones, pistas, o cualquier otra</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cosa que pueda echar a perder la resolucin del problema hasta</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; que hayan pasado por lo menos 60 horas desde el envo de este</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mensaje. Gracias.</FONT>
</P>

<P><FONT SIZE=2>WICHTIG: Bitte schicken Sie keine Lsungen, Tipps oder Hinweise fr</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; diese Aufgabe vor Ablauf von 60 Stunden nach dem Datum dieser</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mail. Danke.</FONT>
</P>

<P><FONT SIZE=2>BELANGRIJK: Stuur aub geen oplossingen, hints of andere tips in de</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eerste 60 uur na het verzendingstijdstip van dit</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bericht. Waarvoor dank.</FONT>
</P>

<P><FONT SIZE=2>Qing3 Zhu4Yi4: Qing3 Ning2 Deng3Dao4 Jie1Dao4 Ben3 Xin4Xi2 Zhi1Hou4 60</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Xiao3Shi2, Zai4 Fa1Biao3 Jie3Da2, Ti2Shi4, Huo4 Qi2Ta1 Hui4</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Xie4Lou4 Da2An4 De5 Jian4Yi4.&nbsp; Xie4Xie4.</FONT>
</P>

<P><FONT SIZE=2>----------------------------------------------------------------</FONT>
</P>

<P><FONT SIZE=2>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.</FONT></P>

<P><FONT SIZE=2>If you've used MakeMaker then you've probably written something like:</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; use ExtUtils::MakeMaker;</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; WriteMakefile(</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; 'Your::Module',</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VERSION_FROM&nbsp; =&gt; 'lib/Your/Module.pm',</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PREREQ_PM&nbsp;&nbsp;&nbsp;&nbsp; =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Test::More' =&gt; 0,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '<A HREF="File::Spec'" TARGET="_blank">File::Spec'</A> =&gt; 0.82,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; },</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; );</FONT>
</P>

<P><FONT SIZE=2>or with Module::Build:</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; use Module::Build;</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; my $build = Module::Build-&gt;new(</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; module_name =&gt; 'Your::Module',</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; license =&gt; 'perl',</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '<A HREF="File::Spec'" TARGET="_blank">File::Spec'</A> =&gt; 0.82,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; },</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; build_requires =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Test::More' =&gt; 0,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ),</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; );</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; $build-&gt;create_build_script;</FONT>
</P>

<P><FONT SIZE=2>Both of the above say tell their respective build tools that <A HREF="File::Spec" TARGET="_blank">File::Spec</A> version 0.82 or above and any version of Test::More are required.</FONT></P>

<P><FONT SIZE=2>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:</FONT></P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Some::Module' =&gt; '&gt;= 0.7, != 1.0, &lt; 2.0',</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; }</FONT>
</P>

<P><FONT SIZE=2>Says that a version of Some::Module of 0.7 or greater, excluding version 1.0 and less than version 2.0 is required.</FONT>
</P>

<P><FONT SIZE=2>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 (<A HREF="http://yaml.org/" TARGET="_blank">http://yaml.org/</A>). 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.</FONT></P>

<P><FONT SIZE=2>The types of things we would like to handle in the requirements specification are boolean expressions &amp;&amp; (and), || (or), and ^^ (xor); grouping with parenthesis; and macro definition and expansion.</FONT></P>

<P><FONT SIZE=2>An example of boolean expressions (suggested by a discussion with David Wheeler and Ken Williams on the module-build list) would be:</FONT></P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; q[</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (DBD::Pg &amp;&amp; DateTime::Format::Pg)</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ||</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (DBD::mysql &amp;&amp; DateTime::Format::mysql)</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; ]</FONT>
</P>

<P><FONT SIZE=2>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:</FONT></P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; q[</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ( DBD::Pg &gt; 1.1 &amp;&amp; DateTime::Format::Pg )</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ||</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ( DBD::mysql &lt;= 1.2 &amp;&amp; DateTime::Format::mysql )</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; ]</FONT>
</P>

<P><FONT SIZE=2>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.</FONT></P>

<P><FONT SIZE=2>Of course we also want to remain compatible as much as possible with the old specifications.</FONT>
</P>

<P><FONT SIZE=2>For the macros, we're mostly interested in the use of predefined macros.</FONT>
<BR><FONT SIZE=2>For example:</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; q[</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ( Term::Readline::Gnu )</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ||</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ( {OSNAME} == MSWin32 &amp;&amp; Term::Readline::Perl )</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; ]</FONT>
</P>

<P><FONT SIZE=2>(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]'.)</FONT></P>

<P><FONT SIZE=2>Other useful macros might be {MULTITHREADED}, {LARGEFILES}, etc.</FONT>
</P>

<P><FONT SIZE=2>Finally, it can be useful to allow definition of macros to simplify expressions. For Example:</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; q[</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def Pg = DBD::Pg &amp;&amp; DateTime::Format::Pg;</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def mysql = DBD::mysql &amp;&amp; DateTime::Format::mysql;</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {Pg} || {mysql}</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; ]</FONT>
</P>

<P><FONT SIZE=2>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:</FONT></P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp; requires =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'db_driver'&nbsp;&nbsp;&nbsp; =&gt; q[ {postgresql} || {mysql} ],</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '{postgresql}' =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'DBD::Pg'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; 0,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'DateTime::Format::Pg'&nbsp;&nbsp;&nbsp; =&gt; 0,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; },</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '{mysql}'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; {</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'DBD::mysql'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =&gt; 0,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'DateTime::Format::mysql' =&gt; 0,</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</FONT>
</P>

<P><FONT SIZE=2>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.</FONT></P>

<P><FONT SIZE=2>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. </FONT></P>

<P><FONT SIZE=2>(Prereq::Expr-&gt;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.</FONT></P>

<P><FONT SIZE=2>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:</FONT></P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <A HREF="http://perl.plover.com/qotw/misc/e024/Versions.pm" TARGET="_blank">http://perl.plover.com/qotw/misc/e024/Versions.pm</A></FONT>
</P>

<P><FONT SIZE=2>Use it like this:</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; use Versions;</FONT>
<BR><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my $version = Versions-&gt;from_module('<A HREF="File::Spec');" TARGET="_blank">File::Spec');</A></FONT>
</P>

<P><FONT SIZE=2>or</FONT>
</P>

<P><FONT SIZE=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my $version = Versions-&gt;from_file('/path/to/module.pm');</FONT>
</P>

<P><FONT SIZE=2>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.</FONT></P>

<P><FONT SIZE=2>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.</FONT></P>

<P><FONT SIZE=2>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.</FONT></P>

<P><FONT SIZE=2>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.</FONT></P>

</BODY>
</HTML>