Friday, 8 August 2014

Premake and Visual Studio

I've been on an interesting journey over the last couple of days and thought it would be good to share what I've learnt.

It started with me wanting to check out bgfx - a very cool looking open source rendering library. I had poked around it in Sublime but wanted to be able to build it from Visual Studio so I could more easily navigate around and debug the code to get a better understanding of how everything works.

I hit a pretty big stumbling block though, and that was having exactly zero knowledge of the tool required to generate the Visual Studio solution - Premake.

Premake is a lot like CMake if you've heard of it - I've used CMake a bunch but have to admit to never writing my own CMakeLists.txt file (it's on my todo list). A number of open source projects I've been interested in have required CMake to generate project and solution files for Visual Studio - there is a bunch of useful information here (I usually just hit Ctrl-F and search for out-of-source build and run that command :))

bgfx required Premake as opposed to CMake. It turned out there wasn't much difference between the two and all you needed in order to use Premake was the Premake executable (4.4 beta5) and to run the command premake4  in the directory where premake4.lua is found for the project you're trying to build - in bgfx's case bgfx\premake. (I wasn't able to just run the make command, I may have missed something but I kind of prefer this approach as it's more explicit in what you're doing)

So in my case I ran - premake4 vs2012

(Update: Note: Premake 4.4b5 is available as part of the bx library too. In order to build bgfx you need to use premake 4.4b5 - Thank you Branimir Karadžić! @bkaradzic

premake4 is the name of the .exe you download from the link above. You can either copy it to where you want to use it or just add it to your Path environment variable (easier in the long run). I specified vs2012 as the argument (unfortunately vs2013 doesn't seem to work with the most recent version of premake4. premake5 does exist, and vs2013 works with this - you can get this by building from source, I also found premake5 here. premake5 won't work with bgfx though - use 4.4b5) Luckily if you do have vs2013, it will just happily update the vs2012 project files and it will all just work (tm).

(Update: premake5 is available here from sourceforge nightlies - Thank you Mihai Sebea! @mihai_sebea)

So now I had the project files for bgfx and I was feeling quite pleased with myself (it doesn't take much). I was about to jump head first into playing around in bgfx but instead decided to have a look at the premake4.lua file used to build it. I had been toying with the idea of switching to CMake or an alternative build tool to create my own solution/project files for my own projects and now seemed as good a time as any to give this a go.

Digression...
As a quick aside I thought it might be worth explaining why do any of this at all. Why not just maintain a Visual Studio .vcxproj and vcxproj.filters file and add/remove all your files from within VS? In real life this is absolutely fine for a lone developer or a small team, but as soon as you have more than a few people working on a project things can start getting unwieldy. It may surprise you to learn that this is actually what we did on Need For Speed: Most Wanted (2012). The .vcxproj and .vcxproj.filters files were both checked into version control, and every time you wanted to add a file, you had to check out the file and attempt to submit before someone else did (locking files was poor form). This lead to bad merges and broken builds. People often refused to undo local changes and re-add the files, they instead would try to hand merge things when there were conflicts and this would quite often go wrong. This wasn't a deal breaker but didn't help things either. Fortunately Frostbite does employ such a system where each time you get latest you more often than not need to regenerate your projects as files will likely have been added/removed from the depot. This eliminates the problems from before, but doesn't come without its drawbacks. Having to generate projects every time you get latest does slow you down (stuff is cached of course but it's not instant, especially with a source base as big as Frostbite) and if you forget to do it you can get some annoying compile/link errors. It's up to you what you do but it's good to know what options are available.
End Digression....

My reason for choosing Premake was first and foremost the documentation and the ease of use. The documentation serves as a fantastic introduction to Premake. It isn't daunting and does not overwhelm the reader with too much information at once. (I've often found the opposite when looking into CMake although I might have just found the wrong sources)

As an experiment I decided to retroactively produce a generation script of a project I am currently working on. This was super helpful as I could compare the project settings in my current solution to the one I was generating to ensure they matched. This took an afternoon of experimentation, googling and cursing until finally I had a script that faithfully produced my full Visual Studio solution.

solution "AS - GL4"
  configurations { "Debug", "Release" }
  location "Project"

-- Awesome Sauce Framework
project "AS"
  language "C++"
  kind "SharedLib"
  location "Project/AS"

files { "GLFW Win/**.h",
"GLFW Win/**.cpp",
"GLFW Win/**.frag",
"GLFW Win/**.vert" }
excludes { "GLFW Win/External/**" }
 
  includedirs { "GLFW Win/External/include/GL/GLFW",
"GLFW Win/External/include/include/assimp",
"GLFW Win/External/include/GL/GLLoadGen",
"GLFW Win/External/include/GL",
"GLFW Win/External/include/glm",
"GLFW Win/External/include",
"GLFW Win/ASframework",
"GLFW Win" }
 
  pchheader "Pch.h"
  pchsource "GLFW Win/ASframework/Pch.cpp"
 
  defines { "AS_FUNCDLL_EXPORT" }

buildoptions { "/DEBUG" }
flags { "Symbols", "FatalWarnings" }
warnings "Extra"

  configuration "Debug"
  buildoptions { "/MDd" }
libdirs { "GLFW Win/External/lib/Debug/assimp",
"GLFW Win/External/lib/Debug/GL3",
"GLFW Win/External/lib/Debug/GLFW",
"GLFW Win/External/lib/Debug/GLLoadGen" }
links { "assimpD" }
optimize "Off"

configuration "Release"
flags { "NoFramePointer" }
libdirs { "GLFW Win/External/lib/Release/assimp",
"GLFW Win/External/lib/Release/GL3",
"GLFW Win/External/lib/Release/GLFW",
"GLFW Win/External/lib/Release/GLLoadGen" }
links { "assimp" }
optimize "Speed"

configuration {} -- Clear configuration

links { "opengl32",
"glfw3",
"GLLoadGen" }

-- OpenGL 4.4 Test Project
project "GL4 Test Bed"
language "C++"
  kind "ConsoleApp"
  location "Project/GL4 Test Bed"

  files { "GL4TestBed/**.h",
  "GL4TestBed/**.cpp",
  "GL4TestBed/**.frag",
"GL4TestBed/**.vert" }

  includedirs { "GL4TestBed",
"GLFW Win" }

buildoptions { "/DEBUG" }
flags { "Symbols", "FatalWarnings" }
warnings "Extra"

configuration "Debug"
libdirs { "GLFW Win/External/lib/Debug/GLLoadGen" }
debugenvs { "PATH=%PATH%;../../GLFW Win/External/dll/Debug;../AS" }
buildoptions { "/MDd" }
optimize "Off"

configuration "Release"
libdirs { "GLFW Win/External/lib/Release/GLLoadGen" }
debugenvs { "PATH=%PATH%;../../GLFW Win/External/dll/Release;../AS" }
flags { "NoFramePointer" }
optimize "Speed"

configuration {} -- Clear configuration

links { "AS",
"opengl32",
"GLLoadGen" }

(As it happens this is actually the premake5.lua script which includes a few small changes - for example the warnings and optimize keywords are additions not included in premake4. Other than that it's much the same - you can find more info about it on the premake wiki)

Many of the options listed are covered in the Premake documentation mentioned before, but there are some interesting additions I had to do some googling for.

One interesting line is debugenvs. This is responsible for this option in the Visual Studio project settings


When you run your application from VS, if there are any other dlls it depends on, this is where you can tell it to look for them. I feel this is much better than just copy/pasting the dlls into the project root folder.

Another is links.These are library dependencies and map to this screen in Visual Studio


I am also using Precompiled Headers in my Library (mainly as an academic exercise to figure out how to set them up, I'm not really interested in if they're a good idea or not) To make sure the premake5.lua script knows this, you need to specify the file and the source for it.

pchheader "Pch.h"
pchsource "GLFW Win/ASframework/Pch.cpp"


The last thing to note is as I am using several other libraries. I have both the debug and release builds of each one and need to link against the correct dll/lib otherwise Visual Studio gives you lots of warnings (and rightly so!). Premake provides the ability to do this with the configuration keyword. Also notice the configuration {} statement which is used to make sure anything following it is not tied to a specific configuration.

I hope that this has given a good introduction to Premake (definitely go read all the docs if you're interested) and that my example script might be of some use as a reference for if you try this yourself. If you have any questions please feel free to shoot me a message (although I am far from an expert having only discovered this a short while ago!)

Thanks for reading - I didn't intend for it to be quite this long!