DEV Community

Juan Burgos
Juan Burgos

Posted on • Updated on

Making my Makefile find things for me

Welp, this is my first actual post on, and what better way to document my slow descent to madness then to dedicate my first post to some "fun" and "cool" stuff I learned while trying to set up a Makefile for a SDL2 game project I'm using as a learning exercise :-)

I'll make this short maybe further expand on stuff in a later post. Also, my apologies of the discussions seems a little hurried and unorganized. This is my first post and I'm writing this later into the evening. Hopefully as time goes on, I'll improve in my writing ability!

Ran into a problem of my own making when pulling the SDL2 MINGW libraries SDL2 Dev, SDL Image, and so on. So... I decided, in my infinite wisdom, that I didn't want to go through each of the MINGW tarballs to find all the includes, libs, and DLLs. Yes, DLLs. Not only did I want to make sure that my future game code can be compiled on Linux, but that I can actually do some coding on my Windows machine.

And I wanted my Makefile to do that all for me.

ifeq ($(OS), Windows_NT)
    ifeq ("$(wildcard $(EXTERNAL_SDL2_DEP))","")
$(error "Failed to locate expected '$(EXTERNAL_SDL2_DEP)' directory containing SDL2 dependencies!")
INCLUDES := $(shell powershell 'Get-ChildItem "include" -Directory -Path "$(EXTERNAL_SDL2_DEP)" -Recurse | Where {$$_.FullName -match "$(PLATFORM_VERSION)"} | Resolve-Path -Relative')
INCLUDES := $(subst \,/,$(addprefix -I, $(addsuffix /SDL2, $(INCLUDES))))
LIBRARIES := $(shell powershell 'Get-ChildItem "lib" -Directory -Path "$(EXTERNAL_SDL2_DEP)" -Recurse | Where {$$_.FullName -match "$(PLATFORM_VERSION)"} | Resolve-Path -Relative')
LIBRARIES := $(subst \,/,$(addprefix -L, $(LIBRARIES)))
SDL_FLAGS+=$(shell sdl2-config --cflags)
Enter fullscreen mode Exit fullscreen mode

Above we can see that I'm specifying x86_64 as my platform of choice... sorry no 32-bit architectures for me. I tossed all the SDL2 release folders I downloaded from Github to my the MINGW-SDL2 folder that sits parallel to my game project directory.

I then check to see if the OS I'm sporting is Windows, and if so, I proceed to run some Powershell commands to automatically locate the includes and libs. Using the addprefix and addsuffix Makefile functions, I construct the -I<path/to/inc> and -L<path/to/lib> parameters that the gcc compiler will require later on.

Minor note 1: Usually when looking through Google and StackOverflow results on how to do certain things in Makefiles, you'll see folks point out the use of $(shell <...>) calls which invoke Bash/Zsh commands. Since I'm in Windows, I want to invoke Powershell commands that do similar things to their Linux counterparts and make sure those commands work when either running in the Powershell shell itself or via some other shell like the MINGW Git Bash shell.

Minor note 2: The use of $$ in the Where {$$_.DirectoryName -match "$(PLATFORM_VERSION)"} Powershell call is important. The $ is already used in a Makefile for referring to variables and executing function calls. In order to use the $ for the Powershell call, you need to "escape" it with another $, hence the $$. You can find this in the Makefile manual online.

To breakdown the Powershell calls a bit...

  • The Get-ChildItem call is sort of like the Bash ls and find commands smashed into a single super command.
  • The Where {...} call (similar to Where-Object) is a simple filter on the PLATFORM_VERSION string x86_64 so we ignore any i686 directories.
  • Finally the Resolve-Path call returns a simple list of directory paths related to the Makefile's location as a newline separate list. When stored in the INCLUDES variable, this result is constructed into a list of relative directory paths.

Let's look at the two lines setting up the $(INCLUDES) variable:

INCLUDES := $(shell powershell 'Get-ChildItem "include" -Directory -Path "$(EXTERNAL_SDL2_DEP)" -Recurse | Where {$$_.FullName -match "$(PLATFORM_VERSION)"} | Resolve-Path -Relative')
INCLUDES := $(subst \,/,$(addprefix -I, $(addsuffix /SDL2, $(INCLUDES))))
Enter fullscreen mode Exit fullscreen mode

Adding additional logging calls $(info INCLUDES: $(INCLUDES)) after each line, you can see this process more clearly.

Here is the result of the first line which located all the include directories for the x86_64 platform:

INCLUDES: ..\MINGW-SDL2\SDL2-devel-2.0.20-mingw\SDL2-
2.0.20\x86_64-w64-mingw32\include ..\MINGW-SDL2\SDL2_image-devel-
2.0.5-mingw\SDL2_image-2.0.5\x86_64-w64-mingw32\include ..\MINGW-
Enter fullscreen mode Exit fullscreen mode

Here is the result of the second line which prepends and appends -I and \SDL2 respectively to each entry and converts all \ to / in the above list to provide the correct linker arguments for the gcc compiler call later on in the Makefile:

INCLUDES: -I../MINGW-SDL2/SDL2-devel-2.0.20-mingw/SDL2-
2.0.20/x86_64-w64-mingw32/include/SDL2 -I../MINGW-SDL2/SDL2_image-
Enter fullscreen mode Exit fullscreen mode

Minor note 1: Setting up the compile flags for Linux is so easy with sdl2-config --cflags ... but doing things in Windows is always more complicated and "fun" for those insane enough to try I suppose :P

Minor note 2: The use of the "walrus operator :=" above is kind of important when setting the INCLUDES variable. Basically evaluates the right-hand and stores the results in the variable. Without it, you'd essentially store the $(shell powershell ...) call in the variable and get a Make error due to the INCLUDES variable being present in its own redefinition. You can find more on this in the Makefile manual online.

As for DLLs, I do the following:

## DLL files and output location where they will be copied
ifeq ($(OS), Windows_NT)
DLLS_SRC  := $(shell powershell 'Get-ChildItem "*.dll" -Path "$(EXTERNAL_SDL2_DEP)" -Recurse | Where {$$_.DirectoryName -match "$(PLATFORM_VERSION)"} | Resolve-Path -Relative')
DLLS_O    := $(addprefix $(BIN_DIR)/, $(notdir $(DLLS_SRC)))
## Copy all the DLLs for Windows build
ifeq ($(OS), Windows_NT)
    $(foreach dll,$(DLLS_SRC),$(shell powershell 'Copy-Item -Path "$(dll)" -Destination "$(BIN_DIR)"'))
Enter fullscreen mode Exit fullscreen mode

Minor note: So it's a bit hacky. I set the $(DLLS_O) list to be all the DLL file names prepended with the game's bin directory path. I use this for the target. Since the Powershell Copy-Item command requires a comma-separated list of files when you want to copy multiple files around and the $(DLLS_SRC) list is space-separated, I opted to use the Makefile foreach(..) function to copy each DLL file separately.

Honestly, there's probably a more "elegant" way to do what I did here. But as you can see, I've used a similar Powershell command to automatically locate all the DLLs and store their relative paths in a list. Later the $(DLLS_O) target runs a foreach command that copies all these DLLs to my game project's bin directory.

Pretty insane ehh? I could have simply copied all the header (.h) files to the appropriate directory along with the library (.a and .la) files and DLL files. But noooo... I had to be simultaneously lazy and insane. But at least I learned something cool doing all this :D

Anyways, this about does it for this diary entry. For now I refuse to use tools like CMake to automatically generate the Makefile since I want to learn how Makefiles work and really get into the nitty-gritty with how things can be set up.

There's still more interesting stuff I'm learning from manually tinkering with my project's Makefile.

See you all next time!

Top comments (0)