Today I was interested in generating some code. I made an external tool that took an input file (.gen) and output a C++ header (.h) file.
Then, I wanted to make this as painless as possible to set up in VS, so that whenever you added a .gen file to the project, it would automatically know how to build it, track dependencies, etc.
My first attempt was of course the familiar "Custom Build Tool". If you add an unknown file type to a VS project and go to its Properties page, you can build the file with a "Custom Build Tool", then fill out a Command Line to run, etc.
Now, this would totally work. Except it's really tedious to maintain. It must be propagated to every configuration your project builds with. It's a giant painful error-prone way to maintain this sort of thing.
There's a better way. As I understand it, as of VS2010, VS is built on top of MSBuild. There's a set of files (a .props, a .targets, and an .xml file) to create your own "Item Type" that can be selected to build files with.
I'm going to lay out a simple example that worked for me. I don't end up using a .props file in my example as I didn't need it, but presumably you can use that to add your own properties that can be set per file for the build step.
Disclaimer: I don't know if this example is completely minimal - I was just happy to actually get it to work. The documentation for how to set this stuff up is really hard to find and dig through.
First, the .xml file (in my example, codegen.xml).
This is nifty and part of the magic. I don't exactly understand the difference between an ItemType and a ContentType, but here's the pragmatic explanation: given this, when you add a .gen file to your project, it will recognize that file as a "CodegenItem" and associate the build step with it. Which is coming next.
Here's the .targets file (codegen.targets) (because of the use of $(MSBuildThisFileName) in this file, it's important that the file name matches the .xml file, so codegen.xml + codegen.targets)
So the first order of business is to 'include' the .xml file we have above, and associate the ItemType that comes from there ("CodegenItem") to the Target we have below ("GenerateCode").
The target is the actual build step. We give it a name "GenerateCode". Next, the BeforeTargets="ClCompile" attribute says that this build step should be run before the ClCompile step (which is the name of the target of the built in C++ compiler). Because I'm generating code, I want this. Next the Inputs and Outputs ("Identity" is the name of the file the build rule is applied to, and Outputs need to be specified so that VS can understand file dependencies).
I thought the Message entry would actually get displayed when the build step as invoked, however it doesn't. I don't see it output in the Build window, but I left it there because I think it should work. It's possible it's being printed to some log file somewhere. Here is the documentation for it: http://msdn.microsoft.com/en-us/library/6yy0yx8d.aspx
Finally, the Exec task (docs here: http://msdn.microsoft.com/en-us/library/x8zx72cd.aspx) - this specifies the commandline to execute. Straightforward - I have a codegen.bat file that takes the input and output file and does its thing.
Now, I would suggest putting these files alongside your .vcxproj. To actually get this to work, open VS, right-click your project and select Build Customizations... In this window, choose Find Existing... and browse to and select your .targets file. Then check the box to enable it. Now, when you add your files to a project, it will have your custom build step associated.
So, there you have it. Now here's the questions I'm left with:
- How do I refer to the Inputs and Outputs of the Target in the command line? I'd like to say %(Inputs) and %(Outputs). Needing to repeat that is redundant and tedious.
- Why doesn't the Message print in the Build window?
- Where's all the friendly introduction docs to this feature?
Thanks to John Calsbeek for a point in the right direction!