CMake/C Plugins for Loadable Commands: Difference between revisions

From KitwarePublic
Jump to navigationJump to search
(How to use CMake's load_command command, in this case to cause CMake to exit with status 42 (as a useless example!). I think that using load_command is generally a bad idea.)
 
(New version of my "exit" example. I needed to fix my sscanf(3) call; %*c does not count as assignment so use %c instead.)
Line 28: Line 28:


<pre>
<pre>
/* commands/cmExitCommand.c */
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
Line 38: Line 40:


void
void
ExitCommandInit(cmLoadedCommandInfo *info) {
ExitCommandInit(cmLoadedCommandInfo *info)
        info->Name = "exit";
{
        info->InitialPass = ipass;
info->Name = "exit";
info->InitialPass = ipass;
}
}


int
int
ipass(void *in, void *mf, int argc, char *argv[]) {
ipass(void *in, void *mf, int argc, char *argv[])
        cmLoadedCommandInfo *info;
{
        int status;
cmLoadedCommandInfo *info;
int status;
char extra;


        info = (cmLoadedCommandInfo *) in;
/* cast to avoid gcc warning */
info = (cmLoadedCommandInfo *) in;


        if (argc != 1) {
if (argc != 1) {
                info->Error =
info->Error =
                    strdup("called with incorrect number of arguments");
    strdup("called with incorrect number of arguments");
                return 0;
return 0;
        }
}


        if (sscanf(argv[0], "%d%*c", &status) != 1) {
if (sscanf(argv[0], "%d%c", &status, &extra) != 1) {
                info->Error =
info->Error =
                    strdup("requires an integer argument");
    strdup("requires an integer argument");
                return 0;
return 0;
        }
}


        exit(status);
exit(status);
        /* NOTREACHED */
/* NOTREACHED */
        return 1;
return 1;
}
}
</pre>
</pre>
Line 71: Line 77:


==== ExitCommandInit ====
==== ExitCommandInit ====
CMake calls the ExitCommandInit function immediately after loading the module. The rule is that a module called cm''SomeName'' needs a ''SomeName''Init function; so a cmExitCommand module needs an ExitCommandInit function. This function must take a pointer to a struct. Here again is the code to ExitCommandInit:
CMake calls the ExitCommandInit function immediately after loading the module. The rule is that a module called cm''SomeName'' needs a ''SomeName''Init function; so a cmExitCommand module needs an ExitCommandInit function. The job of this function is to take a pointer to a struct, and fill the struct with information.
 
Here again is the code to ExitCommandInit:


<pre>
<pre>
void
void
ExitCommandInit(cmLoadedCommandInfo *info) {
ExitCommandInit(cmLoadedCommandInfo *info)
        info->Name = "exit";
{
        info->InitialPass = ipass;
info->Name = "exit";
info->InitialPass = ipass;
}
}
</pre>
</pre>
Line 83: Line 92:
How to write the ExitCommandInit function? CMake initializes all <tt>info</tt> fields to zero (except <tt>info->CAPI</tt>), so this function needs only to set the interesting fields. The most important field is <tt>info->Name</tt> to the name of the command.
How to write the ExitCommandInit function? CMake initializes all <tt>info</tt> fields to zero (except <tt>info->CAPI</tt>), so this function needs only to set the interesting fields. The most important field is <tt>info->Name</tt> to the name of the command.


Every command has an InitialPass and a FinalPass. Most commands need only the InitialPass, a function that takes four arguments (void *info, void *mf, int argc, char *argv[]).
Every CMake command has an initial pass and a final pass. Most commands need only the initial pass. So the line <tt>info->InitialPass = ipass</tt> tells CMake to call our <tt>ipass</tt> function during the initial pass.


==== InitialPass ====
==== InitialPass ====
The <tt>info->InitialPass</tt> is a pointer to a function that takes four arguments <tt>(void *info, void *mf, int argc, char *argv[])</tt> and returns <tt>int</tt>. Because the first parameter declares void * instead of cmLoadedCommandInfo *, we need to cast it. (Or we could just declare <tt>ipass</tt> to take cmLoadedCommandInfo * without a cast, but this would cause a warning in some compilers.)
The declaration for InitialPass uses void *info instead of cmLoadedCommandInfo *info. The code uses a cast to avoid a warning in some compilers.
The declaration for InitialPass uses void *info instead of cmLoadedCommandInfo *info. The code uses a cast to avoid a warning in some compilers.


<pre>
<pre>
        info = (cmLoadedCommandInfo *) in;
/* cast to avoid gcc warning */
info = (cmLoadedCommandInfo *) in;
</pre>
</pre>


InitialPass returns an int value, to be nonzero if the command succeeds, or zero if it fails.
The last two parameters <tt>int argc, char *argv[]</tt> bring in the arguments to the CMake command. The <tt>int</tt> return value is to be nonzero if the command succeeds, or zero if it fails. The "exit" command needs to check that there is one argument, convert the argument from string to integer, and correctly report any error.
 
The correct way to report an error is to set the info->Error string. For some strange reason, CMake will try to free(3) the string later. If you simply assign a string, then CMake will later pass a bogus pointer to free(3). Instead you need to [http://www.freebsd.org/cgi/man.cgi?query=malloc&sektion=3 malloc(3)] the string somehow; this is the reason for the [http://www.freebsd.org/cgi/man.cgi?query=strdup&sektion=3 strdup(3)] calls. After you set the error string, you can return 0 to make the command fail.
 
Also, CMake prepends the name of the command to the error message, so "called with incorrect number of arguments" becomes "''exit'' called with incorrect number of arguments".


The "exit" command needs two error checks; one to check the number of arguments, and another to check that the argument is an integer. (Though builtin CMake commands use [http://www.freebsd.org/cgi/man.cgi?query=atoi&sektion=3 atoi(3)] without an error check.)
The correct way to report an error is to set the info->Error string. For some strange reason, CMake will try to free(3) the string later. If you simply assign a string, then CMake will later pass a bogus pointer to free(3). Instead you need to [http://www.freebsd.org/cgi/man.cgi?query=malloc&sektion=3 malloc(3)] the string somehow; this is the reason for the [http://www.freebsd.org/cgi/man.cgi?query=strdup&sektion=3 strdup(3)] calls. After you set the error string, you can return 0 to make the command fail. Here again is the code to check the number of arguments, and to check the conversion. (Though builtin CMake commands convert with [http://www.freebsd.org/cgi/man.cgi?query=atoi&sektion=3 atoi(3)] and no error check.)


<pre>
<pre>
        if (argc != 1) {
if (argc != 1) {
                info->Error =
info->Error =
                    strdup("called with incorrect number of arguments");
    strdup("called with incorrect number of arguments");
                return 0;
return 0;
        }
}


        if (sscanf(argv[0], "%d%*c", &status) != 1) {
if (sscanf(argv[0], "%d%c", &status, &extra) != 1) {
                info->Error =
info->Error =
                    strdup("requires an integer argument");
    strdup("requires an integer argument");
                return 0;
return 0;
        }
}
</pre>
</pre>
Also, CMake prepends the name of the command to the error message, so "called with incorrect number of arguments" becomes "''exit'' called with incorrect number of arguments".


=== Compiling a module ===
=== Compiling a module ===
Line 125: Line 135:
The following <tt>CMakeLists.txt</tt> file is enough to corectly build the cmExitCommand module:
The following <tt>CMakeLists.txt</tt> file is enough to corectly build the cmExitCommand module:


cmake_minimum_required(VERSION 2.6)
<pre>
project(ExitCommand C)
# commands/CMakeLists.txt
include_directories(${CMAKE_ROOT}/include)
 
add_library(cmExitCommand MODULE cmExitCommand.c)
cmake_minimum_required(VERSION 2.6)
project(ExitCommand C)
include_directories(${CMAKE_ROOT}/include)
add_library(cmExitCommand MODULE cmExitCommand.c)
</pre>


The name of the cmExitCommand.c file is not important; but the name of the cmExitCommand target needs to match with the name of the ExitCommandInit function.
The name of the cmExitCommand.c file is not important; but the name of the cmExitCommand target needs to match with the name of the ExitCommandInit function.


Put this <tt>CMakeLists.txt</tt> and <tt>cmExitCommand.c</tt> in one directory. Configure and build this project, then you have a module to try to load in CMake.
To build this module, for example with Unix make:
 
<pre>
unix$ mkdir commands/obj
unix$ cd commands/obj
unix$ cmake ..
-- The C compiler identification is GNU
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to:
/home/kernigh/trunk/examples/load_command/commands/obj
unix$ make
Scanning dependencies of target cmExitCommand
[100%] Building C object CMakeFiles/cmExitCommand.dir/cmExitCommand.c.o
Linking C shared module libcmExitCommand.so
[100%] Built target cmExitCommand
unix$
</pre>


=== Testing the module ===
=== Testing the module ===
Line 139: Line 174:
The following <tt>CMakeLists.txt</tt> file is such a project:
The following <tt>CMakeLists.txt</tt> file is such a project:


cmake_minimum_required(VERSION 2.6)
<pre>
project(exam C)
# testexit/CMakeLists.txt
# replace ../work with path to module
 
load_command(ExitCommand ../work)
cmake_minimum_required(VERSION 2.6)
exit(freeway)
project(testexit C)
exit(19 left)
load_command(ExitCommand ${CMAKE_SOURCE_DIR}/../commands/obj)
exit(42)
exit(freeway)
exit(19 left)
exit(42)
</pre>
 
: (Replace <tt>${CMAKE_SOURCE_DIR}/../commands/obj</tt> with the path to the built cmExitCommand module.)


When this script runs, the <tt>load_command</tt> succeeds. The first two <tt>exit</tt> commands cause errors. The third such command exits CMake (and skips the usual error message about failing to configure the project). Unix users can use the command <tt>echo $?</tt> to check the exit status from CMake:
When this script runs, the <tt>load_command</tt> succeeds. The first two <tt>exit</tt> commands cause errors. The third such command exits CMake (and skips the usual error message about failing to configure the project). Unix users can use the command <tt>echo $?</tt> to check the exit status from CMake:


<pre>
<pre>
unix$ cmake .
unix$ mkdir testexit/obj
unix$ cd testexit/obj
unix$ cmake ..
-- The C compiler identification is GNU
-- The C compiler identification is GNU
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc
Line 156: Line 198:
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compiler ABI info - done
CMake Error at CMakeLists.txt:4 (exit):
CMake Error at CMakeLists.txt:6 (exit):
   exit requires an integer argument
   exit requires an integer argument




CMake Error at CMakeLists.txt:5 (exit):
CMake Error at CMakeLists.txt:7 (exit):
   exit called with incorrect number of arguments
   exit called with incorrect number of arguments



Revision as of 18:50, 26 December 2008

The normal way to add a new command to CMake is to just define a new macro or function (with the macro and endmacro or function and endfunction commands).

The other way is to use the load_command command, but how does this command work? Actually, this command allows you to code a new CMake command in C or C++, and dynamically load the command into a running CMake. It effectively allows you to insert your own C or C++ code into CMake. The C plugin interface is actually a good example of design of a plugin interface, but it is difficult to use, it has almost no documentation (before the creation of this wiki page), it has several quirks and limitations, it seems leftover from an ancient CMake version, though it remains present in CMake 2.6.

Avoid this feature! Coding a plugin and using load_command is probably not a good idea. This wiki page is for those readers who wonder about load_command or who want to experiment with it. Also, anyone can edit the page and add more information.

Overview

The cmLoadCommandCommand.cxx source file implements a load_command command, which takes the form of

load_command(SomeName dir1 dir2 ...)

This command prefixes "cm" to SomeName, and then loads a cmSomeName module into CMake. (For ELF systems, this would dlopen(3) a "libcmSomeName.so" file into CMake.) It searches for the module in the dir1, dir2, ... directories. Then it looks for and immediately calls the SomeNameInit function in the module.

One can code the module in C or C++, but it must include the cmCPluginAPI.h file, which defines the interface from the module to CMake. The SomeNameInit function must use this interface to define one and only one new CMake command.

EXIT example

How to make CMake exit with status 42?

Wrap the exit(3) C function as a CMake command. For example,

# in CMakeLists.txt
exit(42)

should immediately cause CMake to exit with the given status. This also skips the usual cleaning and error messages, so the exit command will never be useful in any actual CMake project. The exit command intends only a simple example of something that not any macro or function can do.

cmExitCommand.c

This is a C plugin. The entire source is in one file cmExitCommand.c which contains the following:

/* commands/cmExitCommand.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <cmCPluginAPI.h>

void ExitCommandInit(cmLoadedCommandInfo *);
static int ipass(void *, void *, int, char *[]);

void
ExitCommandInit(cmLoadedCommandInfo *info)
{
	info->Name = "exit";
	info->InitialPass = ipass;
}

int
ipass(void *in, void *mf, int argc, char *argv[])
{
	cmLoadedCommandInfo *info;
	int status;
	char extra;

	/* cast to avoid gcc warning */
	info = (cmLoadedCommandInfo *) in;

	if (argc != 1) {
		info->Error =
		    strdup("called with incorrect number of arguments");
		return 0;
	}

	if (sscanf(argv[0], "%d%c", &status, &extra) != 1) {
		info->Error =
		    strdup("requires an integer argument");
		return 0;
	}

	exit(status);
	/* NOTREACHED */
	return 1;
}

That was the entire source code for the exit command. This module does not make any API calls back to CMake (no use of info->CAPI in the code), but it already demonstrates how to cause a command to do something, and how to report errors.

ExitCommandInit

CMake calls the ExitCommandInit function immediately after loading the module. The rule is that a module called cmSomeName needs a SomeNameInit function; so a cmExitCommand module needs an ExitCommandInit function. The job of this function is to take a pointer to a struct, and fill the struct with information.

Here again is the code to ExitCommandInit:

void
ExitCommandInit(cmLoadedCommandInfo *info)
{
	info->Name = "exit";
	info->InitialPass = ipass;
}

How to write the ExitCommandInit function? CMake initializes all info fields to zero (except info->CAPI), so this function needs only to set the interesting fields. The most important field is info->Name to the name of the command.

Every CMake command has an initial pass and a final pass. Most commands need only the initial pass. So the line info->InitialPass = ipass tells CMake to call our ipass function during the initial pass.

InitialPass

The info->InitialPass is a pointer to a function that takes four arguments (void *info, void *mf, int argc, char *argv[]) and returns int. Because the first parameter declares void * instead of cmLoadedCommandInfo *, we need to cast it. (Or we could just declare ipass to take cmLoadedCommandInfo * without a cast, but this would cause a warning in some compilers.)

The declaration for InitialPass uses void *info instead of cmLoadedCommandInfo *info. The code uses a cast to avoid a warning in some compilers.

	/* cast to avoid gcc warning */
	info = (cmLoadedCommandInfo *) in;

The last two parameters int argc, char *argv[] bring in the arguments to the CMake command. The int return value is to be nonzero if the command succeeds, or zero if it fails. The "exit" command needs to check that there is one argument, convert the argument from string to integer, and correctly report any error.

The correct way to report an error is to set the info->Error string. For some strange reason, CMake will try to free(3) the string later. If you simply assign a string, then CMake will later pass a bogus pointer to free(3). Instead you need to malloc(3) the string somehow; this is the reason for the strdup(3) calls. After you set the error string, you can return 0 to make the command fail. Here again is the code to check the number of arguments, and to check the conversion. (Though builtin CMake commands convert with atoi(3) and no error check.)

	if (argc != 1) {
		info->Error =
		    strdup("called with incorrect number of arguments");
		return 0;
	}

	if (sscanf(argv[0], "%d%c", &status, &extra) != 1) {
		info->Error =
		    strdup("requires an integer argument");
		return 0;
	}

Also, CMake prepends the name of the command to the error message, so "called with incorrect number of arguments" becomes "exit called with incorrect number of arguments".

Compiling a module

The cmExitCommand.c file leaves a problem. CMake cannot load a C source file directly, CMake can only load a compiled module.

The output of cmake --help-command load_command suggests using try_compile to compile the module, but try_compile only compiles executables (because it uses add_executable). So you need to create a separate CMake project for the cmExitCommand module, and build this project before loading the cmExitCommand module into any other project.

(It might be possible to trick try_compile to build a module, or at least build an executable that exports its symbols like a module, if you pass the correct compiler flags after the COMPILE_DEFINITIONS keyword. However this would require to discover the correct compiler flags.)

Also, the compiler needs to find the cmCPluginAPI.h header file. CMake installs a copy in ${CMAKE_ROOT}/include.

The following CMakeLists.txt file is enough to corectly build the cmExitCommand module:

# commands/CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(ExitCommand C)
include_directories(${CMAKE_ROOT}/include)
add_library(cmExitCommand MODULE cmExitCommand.c)

The name of the cmExitCommand.c file is not important; but the name of the cmExitCommand target needs to match with the name of the ExitCommandInit function.

To build this module, for example with Unix make:

unix$ mkdir commands/obj
unix$ cd commands/obj
unix$ cmake ..
-- The C compiler identification is GNU
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to:
/home/kernigh/trunk/examples/load_command/commands/obj
unix$ make
Scanning dependencies of target cmExitCommand
[100%] Building C object CMakeFiles/cmExitCommand.dir/cmExitCommand.c.o
Linking C shared module libcmExitCommand.so
[100%] Built target cmExitCommand
unix$

Testing the module

With the compiled module, one can try to load it in a CMake project with load_command and use the new command. Because load_command is not scriptable, you cannot use a cmake -P script to test a module. You must create a test CMake project.

The following CMakeLists.txt file is such a project:

# testexit/CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(testexit C)
load_command(ExitCommand ${CMAKE_SOURCE_DIR}/../commands/obj)
exit(freeway)
exit(19 left)
exit(42)
(Replace ${CMAKE_SOURCE_DIR}/../commands/obj with the path to the built cmExitCommand module.)

When this script runs, the load_command succeeds. The first two exit commands cause errors. The third such command exits CMake (and skips the usual error message about failing to configure the project). Unix users can use the command echo $? to check the exit status from CMake:

unix$ mkdir testexit/obj
unix$ cd testexit/obj
unix$ cmake ..
-- The C compiler identification is GNU
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
CMake Error at CMakeLists.txt:6 (exit):
  exit requires an integer argument


CMake Error at CMakeLists.txt:7 (exit):
  exit called with incorrect number of arguments


unix$ echo $?
42
unix$

That was the story of how, after coding a C plugin, compiling a module and loading the module into CMake, we caused CMake to exit with status 42.

Another Example?

Anyone can edit this wiki page and add more information. The person who wanted to bother could post or link an example of a plugin that actually used info->CAPI for something.