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.
Writing platform-specific code and writing client-specific code is best done with two different techniques.
version
statements for all supported platforms.else static assert(0, "Unsupported platform");
at the end of the version list.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");
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
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.
The version
statement has several limitations that make it less than ideal to customize an application for a client:
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.
version
statement. The complete list of predefined version identifiers is available on this page.3.143.5.201