Writing platform-specific code (versions) and per-client configuration modules

Different platforms often need specialized code. D provides the version statement for this case, which is similar to but cleaner than C's #ifdef directives. In addition to platforms, special code may be needed when building customized versions of an application for a specific client or other special case. While version can do that job, it isn't ideal. So, we'll use another technique: configuration modules.

How to do it…

Writing platform-specific code and writing client-specific code is best done with two different techniques.

Platform-specific code

Perform the following steps:

  1. Write version statements for all supported platforms.
  2. Write else static assert(0, "Unsupported platform"); at the end of the version list.
  3. Don't try to mix partial declarations for different platforms together, it will be more trouble than it is worth.

The code is as follows:

version(Windows)
void writeToFile(HANDLE file, in void[] data) {
  if(!WriteFile(file, data.ptr, data.length, null, null))
    throw new Exception("WriteFile failed");
}
else version(Posix)
void writeToFile(int file, in void[] data) {
  if(write(file, data.ptr, data.length) < 0))
    throw new Exception("write failed");
}
else static assert(0, "Unsupported platform");

Client-specific code

Perform the following steps:

  1. Write separate files for each client, with all the files having the same module name.
  2. Import the module by its module name in the rest of the application.
  3. When compiling, explicitly pass the correct configuration file for the client you are building to the dmd command line:
    // company_x_config.d
    module myapp.config;
    enum bool wantFeatureX = false;
    enum string companyName = "FooBar, Ltd.";
    
    // company_y_config.d
    module myapp.config;
    enum bool wantFeatureX = true;
    enum string companyName = "Acme, Inc.";
    
    // yourapp.d
    import myapp.config;
    static if(wantFeatureX) { void implementFeatureX() {} }
    pragma(msg, "Compiling for client " ~ companyName);

    On compiling you will get the following output:

    dmd yourapp.d company_x_config.d # build for Company X
    dmd yourapp.d company_y_config.d # build for Company Y
    

How it works…

D supports conditional compilation with its version statement. A single simple argument is taken by version: a number or an identifier. Conditions cannot be combined. Version identifiers may be set in a module or on the command line, or preset for the compiler environment and target architecture. If the version is set, the code within is compiled. Otherwise, the code must be syntactically valid but is not actually compiled, and thus does not have to be semantically valid.

Tip

You may use version(none) to quickly disable a block of code. It may come directly before a declaration without curly braces, making it convenient to comment out a function without needing to trace down the end of the function.

The version statement has several limitations that make it less than ideal to customize an application for a client:

  • It cannot cross module boundaries, so all client customizations must be present in the same file. This makes for messy code and may leak private details if one client buys a source license.
  • Data cannot be passed on the command line. Custom strings and other values have to be done with another file of some sort.
  • Boolean conditions cannot be expressed with version. For example, version(a || b) will not compile.

These limitations prompt us to look for alternatives. D presents two easily accessible options: data file imports or code imports. Data file imports are enabled with the –J switch to dmd. You must pass it a directory to search for the files. Then, in your code, you write import("filename"). The file's contents are brought into the code just like a string literal and may be manipulated. We'll use this facility later in the book.

Here, we'll use a regular module import instead. A regular module can consist of any code (since it is just code!), which gives us all the compile-time correctness checking possible in the rest of D.

The module system feature that enables this technique is the fact that the file name need not match the module name, as long as you pass the module explicitly to the compiler. By having multiple files with the same module name, we can use them the same way in the rest of your code, but swap them out at compile time in the make file or on the command line.

This also is preferable to versions because a missing configuration entry will result in a compile-time error, instead of silent acceptance.

See also

  • http://dlang.org/version.html is the official specification for the version statement. The complete list of predefined version identifiers is available on this page.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.5.201