Ampelofilosofies

homeaboutrss
The mind is like a parachute. If it doesn't open, you're meat.

Taming the beast: Visual Studio solution generation for C projects!

01 Oct 2010

My current project - an embedded affair with C code and a whole universe of tools in C, Java, C#, Ruby and in-house DSLs - needs VS support1. So I rolled up my sleeves and proceeded to find a way to generate a Visual Studio solution containing all our production C code, our unit tests, any mock components and the target simulator.

Originally the fastest way seemed to use CMake but that meant maintaining another build tool next to the current rake build system. We considered generating the requisite files from rake and then running CMake which would let us maintain one set of build configurations/scripts etc. but such a solution is too much of Rube Goldberg and introduces all kinds of failure points.

That left me with generating VS project and solution files programmatically using Ruby. I started by creating a single C library project and a single executable project and added the files from our source control, made sure I could compile and link and then took the resulting .vcproj and .sln files and created ERB templates.

Half a day later the code to fill in the templates was complete and I could generate a bunch of project files and a solution. The only problem? It didn't work!

The tale of the aberrant separator

Compilation in the library projects worked, but the linker could no locate the .obj files. See if you can spot the error. The following works:

<Filter Name="Source Files" Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" 
                    UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
  <File RelativePath="..\gen\common\interfaces\IFoo\IFoo.c"></File>
  <File RelativePath="..\gen\common\interfaces\IBar\IBar.c"></File>
</Filter>

This does not:

<Filter Name="Source Files" Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" 
                    UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
  <File RelativePath="../gen/common/interfaces/IFoo/IFoo.c"></File>
  <File RelativePath="../gen/common/interfaces/IBar/IBar.c"></File>
</Filter>

After a lot of fiddling with the generation code, even more Google searches and reading, progress equaled exactly 0.

Turns out, VS parses the relative paths of the files in the project and attempts to extract the basename (the last element of the full pathname). When the paths are defined with '/' as a separator, VS can find the files, open them in the editor and compile them, but it cannot determine the correct basename so it passes the complete relative pathname (with an .obj extension) as a parameter to the linker. Kaboom!

This type of inconsistent software behaviour consumes something like 70% of my time.

What solved the riddle was a game of spot-the-difference between a manually created project file and the generated equivalent.

That took care of the individual projects and allowed project dependencies but there was one more annoying effect: F6 (or ctrl-B) did not work.

The tale of the upward leaning letters

The .sln file is a prime example of a fascination with GUIDs that has gone a bit too far. The section that defines which projects to include when building a solution looks like this:

GlobalSection(ProjectConfigurationPlatforms) = postSolution
  {fdd7a441-7a54-4ead-b8d9-4f23c2ad89b7}.Debug|Win32.ActiveCfg = Debug|Win32
  {fdd7a441-7a54-4ead-b8d9-4f23c2ad89b7}.Debug|Win32.Build.0 = Debug|Win32
EndGlobalSection

The second line is what is added when the check-box is ticked in the build configuration dialog. Only the above snippet will not work.The one that works is:

  GlobalSection(ProjectConfigurationPlatforms) = postSolution
  {fdd7a441-7a54-4ead-b8d9-4f23c2ad89b7}.Debug|Win32.ActiveCfg = Debug|Win32
  {FDD7A441-7A54-4EAD-B8D9-4F23C2AD89B7}.Debug|Win32.Build.0 = Debug|Win32
EndGlobalSection

Yep, it doens't matter if the GUIDs match perfectly and the project files are loaded normally. The only way the build configuration is loaded correctly is if the letters in the GUIDs are upper case. Nowhere else in the .sln file is that necessary.

To make matters worse Visual Studio will silently remove all Build.0 entries it cannot match to projects and rewrite the .sln on exit.

The tale of explicit object destinations

One more possible stumbling block when generating Visual C/C++ projects is the definition of the object file name. The compiler settings translate to:

<Tool Name="VCCLCompilerTool" Optimization="0" MinimalRebuild="true" 
            BasicRuntimeChecks="3" RuntimeLibrary="3"  ObjectFile="$(IntDir)/OBJS/"/>

That trailing '/' in the value of ObjectFile is important. If you leave it out the compiler uses the same .obj name for every .c file and you end up with the binary for a single C file as your library.

Those were the three biggest time wasters while trying to figure out how to create Visual Studio solutions programmatically. For those of you considering this exercise on your own keep the command line reference for the compiler, the linker and librarian handy and skim once the XML Schema for VC project files.

And probably the most important tip is to figure out how the various options translate from Visual Studio dialogs into command line arguments for the compiler and the linker.


1 Visual Studio is considered among the IDE best of breed and the one and only choice really when dealing with .NET managed code or Windows APIs. It's also a beast, in parts slow (adding references or starting unit tests anyone?), in parts unwieldy, memory hungry and with really crappy source control integration (even with TFS).

Granted, each VS version improves on these painful issues and the move to managed code for the suite will prove a boon. Also since I haven't yet put 2010 into production use I can't really pass judgment on how far the improvements have come.

I am not a friend of IDEs (my commenting on Eclipse runs along the same lines - I've just suffered worked longer with VS) and I will go a long way to avoid adding one to my workflow. Mostly due to the difficulties one meets when trying to marry continuous integration and TDD with IDE workflows and the absolute nightmare of handling a multi-languange, multi-platform project.

Some things cannot be avoided though and when working in a team as the tools/test/automation geek you can't really come out and say "Visual Studio is out of the question because I don't like it". If they are good professionals they'll just throw the killer argument in the fray: "but how are we going to debug the code?", otherwise you will have a religious flame-war in your hands.

blog comments powered by Disqus