Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
327 lines (216 sloc) 9.86 KB

NAME

dEx - Dependency Expression Language

SYNOPSIS

  # Create custom macros that can be expanded in other expressions
  define core = File::Spec in [0.80- !0.86] && Cwd > 2;
  define xml  = HAS_LIB('xml2') &&  # <= built-in functions
                HAS_INCLUDE('libxml/tree.h', 'libxml/parser.h');

  # Create tags that can be specified to select a
  # particular branch of an expression.
  choice dbd = (DBD::pg > 1.0 && DateTime::Format::pg) as :pg ||
               (DBD::mysql && DateTime::Format::mysql) as :mysql;

  # The "master" expression; other than assignments, only one expr allowed
  Module::Build#(yaml_support && c_support) &&  # <= req specific features
  (( {OSNAME} in ['*nix' 'MacOS'] && {ITHREADS} &&
     {core} && ( {xml} || XML::Simple ) && {dbd} ) ||
   ( {OSNAME} == 'MSWin32' && {core} && {dbd} ))

DESCRIPTION

A dEx program concists of zero or more macro definitions, and zero or more choice selectors, and a single mandatory master expression. The program produces a true value if the master expression evalutes to true, otherwise the result of the program is false. An empty program is considered true.

In addition to returning true or false, evaluation produces detailed information about what conditions were satisfied and, more importantly, which ones were not.

[Reported failures will likely be filtered. For example, if you there are a string of expressions grouped by AND operators, and one of those expressions is {OSNAME} == 'MSWin32' and the current OS is 'Linux' then that branch can be elminated from the report since there is no sensible way to correct that dependency... Unless there is no alternative, in which case it would be necessary to report that it is incompatible with the current OS.]

Simple Expressions

  • Checking for the presence of a module (any version).

    The simplest expression is one that simply checks for the presence of a module, regardless of version:

      File::Spec

    The above expression will return true if the module is present. In general, it evaluates to the version of the installed module unless the module does not define a version in which case it still evaluates to a true value that is not a version. [???]

  • Checking for particular versions of a module with relational operators.

    You can use relational operators to check that the version of a module is relative to a particular version number:

      File::Spec > 0.80
      File::Spec == 0.86
      File::Spec > 0.80 && File::Spec != 0.85

    The relational operators include: <, <=, >, >=, as well as the equality operators: ==, !=

    The equality operators are overloaded for testing both versions and strings.

    [I think someone requested eq and ne for string comparisons so as to be more perl-like. Technically its simple to provide them as synonyms. But I would caution against trying to look too perl-like. When MB moves to Parrot, it's going to provide a build system for many more languages other than just perl. I originally chose to use one type of operator to make the language definition simpler even though it makes the implementation slightly more complex because of the overloading of the operator for different types of comparison.]

  • Checking for particular versions of a module with the set operator.

    The set operator provides a convenient shorthand for determining if a module's version meets complex requirements. For example the last example in the section above could also be written as:

      File::Spec in [0.80- !0.85]

    The elements of the set are ranges. Ranges can be closed: 0.80-1.0 (from version 0.80 up to and including 1.0) or they can be open on either end -1.0 (any version up to and including 1.0), 1.0- (version 1.0 and greater). If a range is closed then the right-hand side version must be greater than or equal to the left-hand side. Ranges and versions can also be negated !1.0-1.99.

    Successive elements of a set override previous elements, so that if there are any overlaps, the later element wins out. In the previous example:

      File::Spec in [0.80- !0.85]

    a version of 0.85 will fail even thought the first element of the set is a range that includes 0.85 because the subsequent element overrides it.

  • Checking for features

    Some modules provide optional 'features' that may or may not be present or enabled. You can check for the presence of features:

      Module::Build#(yaml_support && c_support)

    Features are specified by following a module name with a hash and one or more feature names in parenthesis separated by the logical and operator &&. Each feature will be checked by using the ...::ConfigData->feature($name) method, as described in the documentation for Module::Build. [???allow all logical operators???]

    [Does it make sense to allow something like: Module::Build#(yaml_support) in [0.20- !0.23] where we check for version in the same expression where we check for features???]

Combining Simple Expressions

More complex expressions can be formed by joining simple expressions with the logical operators: &&, ||, and ^^

The logical and operator, &&, returns true if the expressions on both the left-hand side and the right-hand side are true.

The logical or operator, ||, returns true if either the expression on the left-hand side or the right-hand side are true.

The logical exclusive-or operator, ^^, returns true if only one of the expressions on the left-hand side or the right-hand side are true.

Grouping Expressions

Parenthesis are used to group expression.

Builtin Macros

There are two types of macros. Although they look the same, they function a little differently. Some macros, like {OSNAME} return a string. User defined operators are similar, though in general they generally evaluate to a more complex expression. Other macros, like {ITHREADS}, simply return true or false.

[???Is it an error to evaluate {OSNAME} as a boolean, outside of a comparison???]

{OSNAME}

Evaluates to a string containing the name of the current operating system.

Besides using {OSNAME} with comparitive operators:

  {OSNAME} == 'Linux'

It can also be used with the set operator:

  {OSNAME} in ['Linux' 'BSD' 'MacOSX']
{ITHREADS}

Evaluates to true if ithreads are enabled.

Builtin Functions

HAS_INCLUDE()

Takes a list of one or more strings that are the names of include files, and it returns true if all of the named files are present in the include path.

HAS_LIB()

Takes a list of one or more strings that are the names of libraries, and it returns true if all of the named libraries are present in the library path.

HAS_PROGRAM()

Takes a list of one or more strings that are the names of programs, and it returns true if all of the named programs are present in the path.

Defining and Using Custom Macros

A macro can be defined with the following syntax:

  define <identifier> = <expression> ;

where <identifier> is a valid identifier, and <expression> is any valid expression of arbitrary complexity. For example:

  define dbd = DBD::pg || DBD::mysql;

The above defines a macro named dbd. It can be expanded into another expression by placing the name between braces, e.g.

  define store = BerkeleyDB || {dbd}

Macros are not evaluated at the time they are encountered, although they are parsed and checked for correctness. The reason they are not evaluated immediately is that depending on their placement in the master expression, they may never need to be evaluted. For example, in the last expression above, if BerkeleyDB is present it is unnecessary to check the expression in {dbd}.

Making Choices

Sometimes it's desirable to allow the user to specify constraints on certain expressions. For example, the {dbd} macro we used above:

  define dbd = DBD::pg || DBD::mysql;

Allows for the presence of either DBD driver. Sometimes we would like to be able to eliminate one of those alternatives from consideration. We can do that with the <choice> macro definition operator.

  choice dbd = DBD::pg as :pg || DBD::mysql as :mysql;

When defined this way it still works like normal macro definitions except that now it will accept options which the user can supply. If the user supplies the option 'mysql' to the interpreter, then the test for DBD::pg will be eliminated from the expression when it is compiled, so that it will never be considered during evaluation.

choice statements can be arbitrarily complex. Just place the expression to be tagged in parenthesis.

  choice dbd = (DBD::pg && DateTime::Format::pg) as :pg ||
               (DBD::mysql && DateTime::Format::mysql) as :mysql;

When the interpreter sees an expression like the above, what it is essentially doing is something like:

  define pg    = DBD::pg && DateTime::Format::pg;
  define mysql = DBD::mysql && DateTime::Format::mysql;
  choice dbd   = :pg || :mysql;

The reason for the strange notation of using a colon in front of the macro name is that it allows us to refer to the name of a variable without invoking that name. This is somewhat analogous to Ruby's symbols or to what some languages refer to as atoms.

More about version specifications

Are versions two integers separated by a decimal or decimal numbers? More than one decimal? (as in perl versions) Alpha/Beta versions? Underscores & letters? use version.pm if present or only if included in core (perl 5.8+)?

DETAILS

Types

Package Name
Version
Range
String
Set
Tag

Operators

Relational operators
Equality operators
Set operator
Tag operator
Logical operators
Assignment operator

Operator Precedence

  Relational operators   <, <=, >, >=
  Equality operators     ==, !=
  Set operator           in
  Tag operator           as
  Logical operators      &&, ||, ^^
  Assignment operator    =
Jump to Line
Something went wrong with that request. Please try again.