In Source Code Generation
Code generation is a common task supported by build systems. It is generally discouraged to generate files in the source directory and commit these generated files. However, there are times where one may need to go against this guidance:
- In some development environments, like highly regulated ones, it may be easier to review the generated file(s) than to take the steps necessary to approve a code generation tool.
- One may want to distrubute these generated files for consumers, without forcing the consumers to utilze the code generation tool.
(If you know of other reasons please post them.)
Most build systems will connect any generated code files up to the clean
target. This means that anytime someone runs my_build_system clean
all
generated files will be removed. For generated code that is commited, this
will cause extra burden on the developers as they’ll have to rebuild before
commiting or check back out the now deleted source files.
The trick to work around the build system is to not tell it about the generated code. We’ll go over an example using CMake.
Using CMake to Generate in Source Code
First we’ll create a custom command that does 2 things:
- generate a source file, the
copy
command. - generate a stamp file, the
touch
command.
One will notice that the OUTPUT
only lists the stamp file. The generated
file is not marked as an output. This means that CMake doesn’t know the file
will be created via this custom command. The input file is listed as a
DEPENDS
to ensure anytime a downstream target is built and the stamp
file either doesn’t exist or is older than the input file this command will
run. It is important to note that if the generated source file is deleted and
the stamp file exists and is newer than the input file the custom command
will not be run.
We’ve also created a custom target. For those unfamiliar with CMake the two commands often go together for code generation. The custom target allows for easier referencing from other targets, as well as providing some other benefits.
CMake will complain if a source file is missing, unless it has the
GENERATED property set. However setting the
GENERATED property causes CMake to clean the file. This means we
either need to manually create an empty version of the source file to get
things started. Or one can utilize the [FILE(TOUCH
One issue with generating a compiled source file is that the backend build system, like Ninja, will do an initial pass to see what’s out of date. Since the generated file is not listed as generated, the build system will think it’s up to date and not realize that by invoking the custom command the generated file will be modified. We use the OBJECT_DEPENDS property to let CMake know that if the stamp file changes, it affects the resultant output of the generated source file, and thus the source file will need to be re-compiled.
Caveats
- For headers I haven’t yet found a reliable incremental path for all build backends. In particular Ninja, and guessing make, will do an initial inspection of the build tree to see what is out of date, and then run the build commands. Since the header isn’t advertising itself as generated to CMake, there is no way for Ninja to know to mark users of the header as out of date during the initial inspection of the build tree. One can invoke the build twice to work around this, but this is error prone.