Module Authoring, Continued
Basics of DECLARE_ZORBA_MODULE()
This simple module example uses two of the arguments to DECLARE_ZORBA_MODULE(), namely FILE and URI. These are in fact the only required arguments. (By the way, all arguments may appear in any order.)The FILE argument takes a single value, which is the path to the .xq for the module. If this is a relative path, it will be resolved relative to the current source directory (that is, the directory containing CMakeLists.txt). It may also be an absolute path. It is not required to put double-quotes around the value unless the path contains space characters (this is true of all CMake values). However, if your path includes any CMake variable references, such as "${CMAKE_CURRENT_SOURCE_DIR}/mymod.xq", then it is good practice to surround the value with double-quotes in case the variable ever contains space characters in future.The URI argument also takes a single value, which is the namespace URI of the module file. Note that it is very important that this URI matches the module namespace URI in the .xq file itself. Currently the CMake macros are not advanced enough to parse the XQuery file and verify that this is true.As URIs cannot contain space characters, it is not necessary to surround the value with double-quotes. However, you may do so if it makes the statement more readable for you.A note: the filename of your module (as passed to FILE) may be anything you like. It does not have to have anything in common with the namespace URI. Conventionally, it will be the final component of your URI with ".xq" appended, as in our example where the filename for the namespace URI was mymod.xq. This is a good convention, because once the module is installed into the URI_PATH directory or elsewhere, this is the filename it will have. However, it is not required.
Introducing Versioning
Zorba extends XQuery by offering versioned access to modules (see Versioning Modules). Adding versioning to your modules is very easy. There are two steps: Adding the module-version declaration to your module source code; and passing the VERSION argument to DECLARE_ZORBA_MODULE. The module-version Option
Zorba looks for an XQuery option to define the version of the module. XQuery options are identified by a QName. The specific QName Zorba looks for has the localname module-version, and is located in the namespace . Therefore, put the following two lines into your module, after the module declaration: declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";
Note: As your module grows more complex, you may find that you need to separate the above two lines. The XQuery language requires that all namespace declarations and import statements ( import module and import schema) must occur before any variable, function, or option declarations. So the namespace declaration may end up being earlier in the file than the option declaration. This is fine.Also note that, while the XQuery language allows you to have your variable, function, and option declarations in any order, Zorba requires the module-version option to be declared before any variables or functions in order to function correctly.
The VERSION Argument to DECLARE_ZORBA_MODULE()
Now you must also tell DECLARE_ZORBA_MODULE() the version of the module, so that it can copy it to the correct locations on the URI path. As with the URI argument, it is important that this matches the actual version as declared in the .xq file. DECLARE_ZORBA_MODULE
(FILE mymod.xq URI "http://zorba.io/mymod" VERSION 1.0)
As with URI, version numbers cannot contain space characters, and therefore double-quotes surrounding the value are optional.That's it! Now query authors can import your module using the version fragment, as discussed in Versioning Modules.You may declare multiple versions of the same module (that is, with the same namespace URI), so that you can for instance maintain and contine to ship version 1.x of your module even after you have developed version 2.x. However, there is one limitation to DECLARE_ZORBA_MODULE() regarding this: You must ensure that you declare all versions of the same module in decreasing version number order, that is, starting with the highest version number and working backwards. So, for example, if you are shipping versions 3.1, 2.4, and 1.6 of your module, you may have: DECLARE_ZORBA_MODULE
(FILE mymod-3.xq URI "http://zorba.io/mymod" VERSION 3.1)
DECLARE_ZORBA_MODULE
(FILE mymod-2.xq URI "http://zorba.io/mymod" VERSION 2.4)
DECLARE_ZORBA_MODULE
(FILE mymod-1.xq URI "http://zorba.io/mymod" VERSION 1.6)
If you do this incorrectly (out of order), you will get an error when you invoke CMake, so it is not possible to accidentally do it wrong.(Note that the above example also demonstrates, as noted earlier, that the filename of your module .xq file need not directly correspond with the namespace URI. You may use different filenames for supporting different versions if you wish.)Finally, note that if you declare a module without a VERSION argument, it will internally be treated as though the version was "0.0.0".
Adding schemas
If you develop modules that make use of your own XML Schemas, then you should keep those schemas as part of your module project and install them. Zorba provides a macro DECLARE_ZORBA_SCHEMA() for this purpose, which works almost identically to DECLARE_ZORBA_MODULE(): DECLARE_ZORBA_SCHEMA
(URI "http://zorba.io/myschema" FILE "schema.xsd")
The semantics and meanings of the URI and FILE arguments are the same.One important distinction is that Zorba does not currently support versioning of schemas, so there is no VERSION argument to DECLARE_ZORBA_SCHEMA(). We hope to add this functionality in a later release.
Writing Tests
Now that you've written a module, it would be a great idea to write some test cases for it to ensure that later changes don't break any functionality. (In fact, if you subscribe to the Test-Driven Development philosophy, you probably want to write the tests first!) Zorba ships a testdriver program and macros to easily add XQuery-based tests to your project. Basic Testing
The basic steps are:1. Create a test directory with two subdirectories named "Queries" and "ExpQueryResults". Here we assume that directory is named tests and is located at the top of your project.2. Inside Queries, create XQueries in files with the extension .xq. You may use any directory structure you like inside this directory to organize your test queries.3. Inside ExpQueryResults, create expected results files for your queries with the extension .xml.res. The directory structure should be exactly the same as inside Queries. For instance, if you have a test in the file tests/Queries/feature-A/test-1.xq, then the expected results should be in tests/ExpQueryResults/feature-A/test-1.xml.res.4. In your CMakeLists.txt, add the following lines near the top - for example right after the PROJECT (...) line: ENABLE_TESTING ()
INCLUDE (CTest)
5. Finally, elsewhere in your CMakeLists.txt, add: ADD_TEST_DIRECTORY (tests)
That's it! Now, from your build directory, after re-building your project, you can run all your tests by simply executing the command ctest. CTest is a testing framework that ships with CMake. It has a great many features, but the two most important command-line options are:1. ctest -R [pattern] will execute only those tests whose name matches the given pattern. Since ADD_TEST_DIRECTORY() creates the test names based on the directory structure under Queries, this makes it easy to run a subset of your tests. For instance, ctest -R feature-A will run all the tests in the "feature-A" directory.2. ctest -V will show the output of the tests, rather than just a synopsis of the results. This is useful when debugging. Since the output of testdriver is fairly verbose, it is best to combine -V with a -R option that limits ctest to exactly one test; for example: ctest -V -R feature-A/test-1.
More Advanced Testing
Zorba's testdriver has several other features - it is actually the same test program used by Zorba itself both for its own internal testing as well as running the W3C's XQuery Test Suite conformance suite, which has a number of complex requirements.testdriver activates additional features when a test query has a corresponding .spec file. This is a file in the same directory as an .xq test query, with the same file name but a .spec extension. For instance, for the test mentioned in step 3 above, the spec file would be tests/Queries/feature-A/test-1.spec. We will briefly document the two most useful features here.1. Negative tests: If you would like to test error conditions, you can add a line such as the following to your .spec file: Error: http:
This tells testdriver to expect that the query will raise an error with the given QName. If any other error, or no error at all, is raised, then the test will fail. In this case, it is not necessary to have an expected results .xml.res file, as the expected result is instead the error.2. Binding external variables: If your query has external variables, you may bind them with lines in your .spec file such as the following: Args:
-x
var:=value
(Unfortunately, it is necessary to put the above assignment exactly as shown, on three separate lines in the .spec file.)This will bind the string value "value" to the variable $var in the query.You may also bind an XML file as a document variable by using "=" instead of ":=" in the assignment: Args:
-x
var=$RBKT_SRC_DIR/input.xml
This will parse the specified XML file and bind the resulting document node to the variable $var.The expression $RBKT_SRC_DIR will be replaced by the full path to the directory that was originally passed to ADD_TEST_DIRECTORY(). (The named $RBKT_SRC_DIR is a strange historical artifact of the early days of Zorba testing.)
External Functions in C++
The final advanced feature of DECLARE_ZORBA_MODULE is building a dynamic library for any external module functions that are implemented in C++. For information about actually writing these functions, see Writing Your Own External Functions. Here we describe how DECLARE_ZORBA_MODULE() automates the building and distributing of these libraries.If you have a module with external function declarations named module.xq, then you simply create a subdirectory named module.xq.src in the same directory as the .xq file. DECLARE_ZORBA_MODULE() will identify this directory and:1. Add build rules to your project that compiles all .cpp files in the .xq.src directory (or any subdirectories of that directory)2. Link them together into an appropriately-named shared library for your operating system3. Copy the resulting shared library into <build_dir>/LIB_PATH
with the correct name and location, and4. Create an INSTALL rule so that the shared library will be installed correctly along with your project. It will be automatically installed into Zorba's default non-core library path (see Zorba's Library Path).If your code depends on other dynamic libraries, you can pass the full paths to those libraries to DECLARE_ZORBA_MODULE() using the LINK_LIBRARIES option. In most cases, you will have this full path available because you used CMake's FIND_PACKAGE() command to locate it. For example, here is a bit of the CMakeLists.txt for the non-core XSLT module, which depends on libxslt: FIND_PACKAGE (LibXslt)
IF (LIBXSLT_FOUND)
INCLUDE_DIRECTORIES (${LIBXSLT_INCLUDE_DIR})
DECLARE_ZORBA_MODULE (FILE xslt.xq VERSION 1.0
URI "http://zorba.io/modules/xslt"
LINK_LIBRARIES "${LIBXSLT_LIBRARIES}")
ENDIF (LIBXSLT_FOUND)
Note that while DECLARE_ZORBA_MODULE will set up all the above build rules for your source code, it is still your responsibility to ensure that those .cpp files can find the necessary header files by using CMake's INCLUDE_DIRECTORIES() command. Again, generally speaking the path to the appropriate include directory will be set in a CMake variable by the FIND_PACKAGE() command.Important note: DECLARE_ZORBA_MODULE discovers all .cpp files in the source directory automatically, using a glob pattern (i.e., *.cpp). This is convenient, but it does have a hidden downside: If you add new .cpp files to this directory, CMake has no way of knowing that you have done so, and so those files will not automatically get built. This can lead to strange compilation or runtime errors. Fortunately, there is a simple solution: Whenever you add new .cpp files for a module, always remember to immediately re-run cmake in your build directory. This will cause the glob pattern to be re-run and will pick up any new source code files. Likewise, if you delete any .cpp files from your source directory, you will need to re-run cmake for the same reason - although at least in that case you will get a clear error message if you forget to do so.
Zorba Module CMake Macros: Summary
For reference, here is the complete set of options for all macros provided by Zorba: DECLARE_ZORBA_MODULE (FILE <.xq filename>
URI <module URI>
[ VERSION <version number> ]
[ LINK_LIBRARIES <library> [ <library> ...] ]
[ TEST_ONLY ] )
DECLARE_ZORBA_SCHEMA (FILE <.xq filename>
URI <module URI>
[ TEST_ONLY ] )
ADD_TEST_DIRECTORY (<test directory>)
DONE_DECLARING_ZORBA_URIS ()
|