A lot of people asked for a sample on how to compile a project from within Visual Studio Code and how to get nice formatted compiler messages. VSCode comes with a configurable interface that allows you to define
- a general command to be executed on each build
- parameters to that command to build the main project
- parameters to that command to build and run the test project
- and a problem matcher that extracts warnings and errors from the compiler output.
This configuration is stored in ${workspaceRoot}/.vscode/tasks.json
where ${workspaceRoot}
is the path of the folder opened in VSCode.
Technical overview
We create a batch file which accepts two different values as the first parameter – build
and test
. Depending on its value we execute the corresponding commands. We define a problem matcher in order to convert the compiler’s StdOut
into displayable notifications, hints and errors.
After compiling the test command we call the test executable to see its result inside the editor.
Here are two sample configurations. Pick the one that helps you the most.
Configuration for a Delphi project using MSBuild
Compile.bat:
@echo off
SET MSBUILD="C:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe"
SET RSVARS="C:\Program Files (x86)\Embarcadero\Studio\16.0\bin\rsvars.bat"
if /i %1%==test (
SET PROJECT=TestProject\UnitTests.dproj
) else (
SET PROJECT=MainProject\AwesomeExe.dproj
)
call %RSVARS%
%MSBUILD% %PROJECT% "/t:Clean,Make" "/p:config=Debug" "/verbosity:minimal"
if %ERRORLEVEL% NEQ 0 GOTO END
echo.
if /i %1%==test (
TestProject\UnitTests.exe
)
:END
tasks.json:
{
"version": "0.1.0",
"windows": {
"command": "${workspaceRoot}/Compile.bat"
},
"isShellCommand": true,
"showOutput": "always",
"tasks": [
{
"taskName": "build",
"isBuildCommand": true,
"problemMatcher": {
"owner": "external",
"fileLocation": ["relative", "${workspaceRoot}"],
"pattern": {
"regexp": "((([A-Za-z]):\\\\(?:[^\\/:*?\\\"<>|\\r\\n]+\\\\)*)?[^\\/\\s\\(:*?\\\"<>|\\r\\n]*)\\((\\d+)\\):\\s.*(fatal|error|warning|hint)\\s(.*):\\s(.*)",
"file": 1,
"line": 4,
"severity": 5,
"code": 6,
"message": 7
}
}
},
{
"taskName": "test",
"isTestCommand": true,
"problemMatcher": {
"owner": "external",
"fileLocation": ["relative", "${workspaceRoot}"],
"pattern": {
"regexp": "((([A-Za-z]):\\\\(?:[^\\/:*?\\\"<>|\\r\\n]+\\\\)*)?[^\\/\\s\\(:*?\\\"<>|\\r\\n]*)\\((\\d+)\\):\\s.*(fatal|error|warning|hint)\\s(.*):\\s(.*)",
"file": 1,
"line": 4,
"severity": 5,
"code": 6,
"message": 7
}
}
}
]
}
Known issues
- The Delphi compiler provides file name and line number but it doesn’t provide the column number where a message belongs to. VSCode has to guess the correct column. This can cause the error marks to appear in the wrong columns within the correct lines.
-
File names are reported with paths relative to the
.dproj
file when the file is part of the project. When a file is not part of the project but inside the search path the Delphi compiler uses the absolute path for that file. VSCode’s problem matcher currently can’t handle both types of file names at the same time. You need to decide via thefileLocation
key which kind of file names you want to support. You can’t open messages with relative file names when you set"fileLocation": ["absolute"]
and vice versa.
Configuration for a Lazarus project using lazbuild
Compile.bat:
@echo off
SET LAZBUILD=C:\development\lazarus\lazbuild.exe
if /i %1%==test (
SET PROJECT=TestProject\UnitTests.lpi
) else (
SET PROJECT=MainProject\AwesomeExe.lpi
)
%LAZBUILD% %PROJECT% --verbose
if %ERRORLEVEL% NEQ 0 GOTO END
echo.
if /i %1%==test (
TestProject\UnitTests.exe --format=plain -a
)
:END
tasks.json:
{
"version": "0.1.0",
"windows": {
"command": "${workspaceRoot}/Compile.bat"
},
"isShellCommand": true,
"showOutput": "always",
"tasks": [
{
"taskName": "build",
"isBuildCommand": true,
"severity": "info",
"problemMatcher": {
"owner": "external",
"fileLocation": ["absolute"],
"pattern": {
"regexp": "(([A-Za-z]):\\\\(?:[^\\/:*?\"<>|\\r\\n]+\\\\)*[^\\/\\s\\(:*?\"<>|\\r\\n]*)\\((\\d+),(\\d+)\\)\\s.*(Fatal|Error|Warning|Hint|Note):\\s\\((\\d+)\\)\\s(.*)$",
"file": 1,
"line": 3,
"column": 4,
"severity": 5,
"code": 6,
"message": 7
}
}
},
{
"taskName": "test",
"isTestCommand": true,
"severity": "info",
"problemMatcher": {
"owner": "external",
"fileLocation": ["absolute"],
"pattern": {
"regexp": "(([A-Za-z]):\\\\(?:[^\\/:*?\"<>|\\r\\n]+\\\\)*[^\\/\\s\\(:*?\"<>|\\r\\n]*)\\((\\d+),(\\d+)\\)\\s.*(Fatal|Error|Warning|Hint|Note):\\s\\((\\d+)\\)\\s(.*)$",
"file": 1,
"line": 3,
"column": 4,
"severity": 5,
"code": 6,
"message": 7
}
}
}
]
}
When it’s done
When everything works fine then you can
- run the build command by pressing
CTRL+SHIFT+B
- run the test command by pressing
CTRL+SHIFT+T
- jump to the next compiler message in the current file with
F8
- jump to the previous compiler message in the current file with
SHIFT+F8