Introduction
Life would be so much easier if some things were more compatible. Like having no need for a power plug adapter when flying over the Atlantic. Like not caring which battery type you need for your gadget. Like using your favorite IDE with any compiler you might need for the embedded project...
Wait a minute, we're software developers, we are supposed to know how to make things compatible. This article describes how to make Visual Studio "understand" the error messages produced by GCC using a Makefile project and a simple PERL script.
Although Visual Studio is a complex IDE with numerous functions, the way it compiles C/C++ code is pretty straight-forward: it simply executes Microsoft C++ Compiler (cl.exe) and parses its output to get the error/warning list. So does it mean we need to fully emulate the behavior of cl.exe version 15 (with the enormous amount of command-line switches so typical for a version 15 of a tool)? Fortunately, there is an easier way.
Makefile projects
Visual Studio supports a special project type - makefile project. It is originally intended to be used with the Microsoft make tool (NMake), but in fact it will simply run a given command to build or clean the solution; and what's more important, it will expect it to report errors in a way compatible with cl.exe.
So let's try to create a Visual Studio project to build an ARM firmware file using GCC. Before we begin, make a batch file (we call it build.bat) that either invokes GNU make, or just calls GCC to compile your code. To keep things simple, I'll use a tiny "LED blink" demo for the AT91SAM7S256 microcontroller to illustrate. In this case, the built.bat file will just contain 1 line:
C:\SysGCC\arm-eabi\bin\arm-eabi-g++.exe testled.cpp ../shared/board_cstartup.o ../shared/board_lowlevel.o -nostartfiles -DAT91SAM7S256 -I../shared/at91sam7s256 -T../shared/at91sam7s256/flash_noremap.lds -o testled.elf
If testled.cpp contains errors, arm-eabi-g++.exe will report them. So let's make Visual Studio understand its output and help us navigate among the error list:
1. Open Visual Studio.
2. Choose File->New->Project and then select Visual C++->General->Makefile project.
3. Specify the directory where you have your embedded project.
4. Specify "build.bat" as your command line.
5. Add your source files to the project, so that Visual Studio can recognize them
So what happens when we make a mistake in the source file and try to build the program? Visual Studio will run build.bat, build.bat will run GCC, GCC will report the errors and Visual Studio will show its output.
The problem here is that those error messages are not clickable. You can see them as clear text, but Visual Studio won't open the source file when you double-click on them. They also won't appear in the error list window. This happens due to different error message formats used by Visual Studio and GCC. GCC error messages look like this:
<file_name>:row:column: error: <text>
In turn, Visual Studio, expects something like that:
<file_name>(row,column): error: <text>
A tiny difference, but just enough for Visual Studio to fully ignore those messages and make you search line numbers manually.
The script
The way to fix the problem is pretty straight-forward. We'll make a script that takes the output produced by built.bat and converts the error messages. Furthermore, there is a bit more trickery besides just inserting brackets and converting colon to comma:
- We need to convert relative paths to absolute.
- We need to convert cygwin-style (or mingw-style) paths to normal Windows paths.
- We need to convert the "c:/xxx" paths to "c:\xxx"
One of the quickest ways to make such a script is using the PERL language. It has its pros and cons, but it's probably one of the easiest ways to make simple scripts below 100 lines of code that have to do some simple text processing.
First, we need to change the build command line in the Make Project settings:
build.bat 2>&1 | E:\Perl\bin\perl.exe gccfilt.pl
If you don't have perl.exe installed, you can get it by installing cygwin environment.
The "2>&1" construct means "join STDERR together with STDOUT" or in simple words "pass error messages through the script as well".
Second, let's make the script. We'll detect the GCC error messages using 2 regular expressions and then try to transform the paths using the unix2winpath() function that we'll define later:
$mydir = `cmd /c cd`;
chomp $mydir;
foreach (<STDIN>)
{
if (/^([^ ]+):([0-9]+):([0-9]+): (.*)$/)
{
print unix2winpath($1)."($2,$3) : $4\n";
}
elsif (/^(In file included from )([^ ]+):([0-9]+):([0-9]+):$/)
{
print unix2winpath($2)."($3,$4) : <==== Included from here (double-click to go to line)\n";
}
else
{
print "$_";
}
}
Now let's define the path transformation function:
sub unix2winpath
{
my $fp = $_[0];
#Handle "c:/xxx" paths
if (substr($fp,1,1) eq ':')
{
$fp =~ s/\
return $fp;
}
#Handle relative paths
if (substr($fp,0,1) ne '/')
{
$fp =~ s/\
return "$mydir\\$fp";
}
}
Finally, we need to convert the absolute paths to the Windows format. Instead of hardcoding Cygwin/MinGW path templates, we'll use the "mount" command to extract them:
foreach(`mount`)
{
if (/^([^ \t]+) on ([^ \t]+) /)
{
if ($2 eq '/')
{
$ROOTMOUNT = $1;
}
else
{
$MOUNTS{$2} = $1;
}
}
}
Finally, let's use this information in the unix2winpath() function:
foreach(keys %MOUNTS)
{
if ($fp =~ /^$_\/(.*)$/)
{
my $suffix = $1;
$suffix =~ s/\
return "$MOUNTS{$_}\\$suffix";
}
}
$fp =~ s/\
return $ROOTMOUNT.$fp;
Here it goes. The GCC messages are transformed on-the-fly and Visual Studio handles them the same way it would handle the messages from cl.exe. Double-click on a message to navigate to a line, or use the error list. And, of course, don't forget about the F8 shortcut that quickly jumps you to the next error.