SDL and VSCode

I recently took the time to set up a new VSCode project to work with SDL as a learning exercise. To do this, I adapted this LazyFoo tutorial for Visual Studio. The whole process probably took me an hour or so (less than it took me to write this blog post, sadly).

Let’s start off by looking at the project layout.

Directory Layout

Directory Structure

Folder Description
.vscode Project specific settings for VSCode (kind of like a .vcproj/.sln file)
build Output directory for the compiler and linker (can be safely deleted – sometimes called bin)
code Source code directory
data Data directory (e.g. fonts, sprites, sounds, levels)
external 3rd party libraries/code (e.g. SDL)
scripts Build and utility scripts

Build Philosophy

I follow the same build philosophy that Casey Muratori employs for Handmade Hero. I’m going to re-iterate here, but I feel like Casey put it best in the first episode of his Handmade Hero series. He also summed up my feelings pretty well on twitter:


Like Casey (but perhaps for slightly different reasons), I use shell scripts (.bat files). In fact, I’ve been doing this since working on Megamind at THQ. I don’t like using python, perl, cmake, ninja or any of that. Mostly because I don’t want the overhead costs that those tools incur in setup, maintenance, and polluting my windows environment.

I also want to understand and control (to the greatest extent possible) everything to do with compiling, linking, publishing and cleaning my project. Using build tools obfuscates the process and makes things more complicated for little to no payoff (especially when it comes to smaller personal projects). My machine is so fast there really is no need for something to try to speed up build times or manage these processes in any way.

Scripts

In the same fashion as Casey, I have a shell.bat file that runs when I open a command window to correctly set up the build environment. To do this, I create a shortcut in my project directory with the target set to:

%windir%\system32\cmd.exe /k .\scripts\shell.bat

I use the VC++ compiler (cl.exe), so this script simply sets up all the variables used by the compiler and adds my script directory to the path so I can easily run any of my project scripts (e.g. build.bat). It’s important to note that I am currently only targeting x64, but as I add more target platforms these scripts will be updated to take a platform parameter.

@echo off

set scriptsDir=%~dp0

cd /d %scriptsDir%..

:: uncomment the line below line to debug the vcvars
:: set VSCMD_DEBUG=1
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"

set path=%scriptsDir%;%path%

:: Add dependencies to our include and lib paths (used by cl.exe)
set INCLUDE=%scriptsDir%..\external\sdl\include;%INCLUDE%
set LIB=%scriptsDir%..\external\sdl\lib\x64;%LIB%

Once the environment has been set up by the shell script I’m able to run the build script.

@echo off

set buildDir=%~dp0..\build

if not exist %buildDir% mkdir %buildDir%

pushd %buildDir%

:: compiler input
set objDir=.\obj\
set outputExe=Learn_SDL
set libs=SDL2.lib SDL2main.lib
set files=..\code\main.cpp

:: compiler flags:
:: /Zi enable debugging information
:: /FC use full path in diagnostics
:: /Fo<path> the path to write object files
:: /Fe<path> the path to write the executable file
set compileFlags=/Zi /FC /Fo%objDir% /Fe%outputExe% 

:: linker flags:
:: /SUBSYSTEM specifies exe env - defines entry point symbol
set linkFlags=/link /SUBSYSTEM:CONSOLE

if not exist %objDir% mkdir %objDir%

cl %compileFlags% %files% %libs% %linkFlags%

:: Copy dependencies
xcopy /y ..\external\SDL\lib\x64\SDL2.dll .

popd

The clean script removes the build files if I want to do a fresh build from scratch. It’s true that I could outright delete the build directory (and I still might change this script to do just that) but I wanted to be precise about what I was deleting to understand what’s there.

@echo off

set buildDir=%~dp0..\build
set objDir=.\obj\

if exist %buildDir% (  
  pushd %buildDir%
  del /q /s *.exe *.pdb *.ilk *.dll
  rd /s /q %objDir%
  rd /s /q .vs
  popd
)

VSCode Setup

I run these scripts from VSCode via tasks. I have a build task and a clean task. The build task first runs the shell script to set up the environment then runs the build script. The default shortcut is ctrl+shift+b. I use the problem matcher so that compilation errors and warnings show up correctly in the problems window and can be navigated using shortcuts.

To edit tasks ctrl+shift+p and select Tasks:Configure Task Runner

{
    "version": "0.1.0",
    "command": "cmd",
    "isShellCommand": true,
    "args": ["/c"],
    "showOutput":"silent",
    "echoCommand": true,
    "tasks": 
    [
        {
            "taskName": "build", 
            "suppressTaskName": true,
            "isBuildCommand": true,
            "isShellCommand": true,
            "args": [ "\"scripts\\shell & build\"" ],
            "problemMatcher": "$msCompile"
        },

        {
            "taskName": "clean", 
            "suppressTaskName": true,
            "isShellCommand": true,
            "args": [ "\"scripts\\clean\"" ]
        }
    ]
}

You can keybind custom tasks in VSCode as of a recent update, so I do that for the clean script.

To edit keybindings ctrl+shift+p and select Preferences:Open Keyboard Shortcuts File

// Clean build
{
    "key": "ctrl+shift+c",
    "command": "workbench.action.tasks.runTask",
    "args": "clean"
},

Right now my launch configuration is extremely basic. It uses the data directory as the working directory and attaches to my executable.

To edit launch config ctrl+shift+p and select Debug:Open launch.json

    {
        "name": "C++ Launch (Windows)",
        "type": "cppvsdbg",
        "request": "launch",
        "program": "${workspaceRoot}/build/Learn_SDL.exe",
        "args": [],
        "stopAtEntry": false,
        "cwd": "${workspaceRoot}/data",
        "environment": [],
        "externalConsole": false
    },

To enable intellisense I had to setup my include paths. VSCode generated almost all of of it for me but I had to add the include lines for SDL. Note that This has nothing to do with compilation and is purely to aid in code navigation.

To edit cpp config ctrl+shift+p and select c/cpp:Edit configurations

{
    "name": "Win32",
    "includePath": [
        "${workspaceRoot}",
        "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.10.25017/include/*",
        "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/um",
        "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/ucrt",
        "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/shared",
        "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/winrt",
        "${workspaceRoot}/external/SDL/include"
    ],
    "defines": [
        "_DEBUG",
        "UNICODE"
    ],
    "browse": {
        "path": [
            "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.10.25017/include/*",
            "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/um",
            "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/ucrt",
            "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/shared",
            "C:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0/winrt",
            "${workspaceRoot}/external/SDL/include"
        ],
        "limitSymbolsToIncludedHeaders": true,
        "databaseFilename": ""
    }
}

Finally there are my workspace (aka project) specific settings. Since I’m the only one working on the project these could just be included in my global settings file instead, but there are a few settings that I’d like to stay with the project no matter what. For example, I never want to add the workspace root recursively to the include path. I’d rather just specify directly my includes. I also keep the tab size in here in case I forget to set it on a new setup/machine I start working on.

To edit workspace settings ctrl+shift+p and select Preferences:Open Workspace Settings

{
    "editor.tabSize": 4,
    "C_Cpp.addWorkspaceRootToIncludePath": false
}

Leave a Reply