Key Points for Writing Makefiles in Windows Environment

(Star this for CPP developers to enhance C/C++ skills)

Author: Zhu Jincan (This article is submitted by the author)

1. Basic Patterns of Makefiles in Windows Environment

From my observation, there is a basic pattern for makefiles in the Windows environment, which is as follows:

1. Set compiler flags

2. If the compiler flags are incorrect, prompt usage

3. If the clean command is executed, perform the cleaning command

4. Set compilation output (including output folder, exe file, or dll file, etc.)

5. Create output folders, etc. (this step is optional)

6. Run the compilation command to generate target files

7. Run resource compilation command (if there are no resource files, this step is unnecessary)

8. Run the link command to generate exe or dll files

2. A Makefile Template I Designed

Many open-source libraries set various compilation options. For example, the GDAL library has the following different compilation commands:

nmake -f makefile.vc DEBUG=1 // Compile 32-bit debug version

nmake -f makefile.vc // Compile 32-bit release version

nmake -f makefile.vc WIN64=1 DEBUG=1 // Compile 64-bit debug version

nmake -f makefile.vc WIN64=1 // Compile 64-bit release version

nmake -f makefile.vc clean // Clean compilation

nmake -f makefile.vc devinstall WIN64=1 // Compile release version and install development header and library files
Such a design is not user-friendly, as users cannot remember so many compilation options. A user-friendly design should have only one compilation option, and users determine what version to compile based on this option, for example, nmake /f makefile.vc option=x, where x ranges from 1 to 5. Let’s illustrate with my example:
# Include basic environment variables
!include <C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include\Win32.Mak>
# Set compiler flags, initialize to FALSE
CFGSET     =  FALSE

!IF "$(option)"== "1"
# The following is the setup for compilation option 1
CFGSET  =  TRUE

Platform= x64
Configuration= Release
# Define preprocessor for release version
CC_OPTION   = -D_WIN64 -D_NDEBUG -D_WINDOWS

TARGET_MACHINE = X64

!ELSE IF "$(option)"== "2"
# The following is the setup for compilation option 2
CFGSET   =  TRUE

Configuration= Debug
Platform= x64
# Define compilation options for debug version
CC_OPTION    = -D_WIN64 -D_DEBUG -D_WINDOWS

TARGET_MACHINE = X64

!ELSE IF "$(option)"== "3"
# The following is the setup for compilation option 3
CFGSET   =  TRUE

Configuration= Release
Platform= Win32
CC_OPTION   = -D_WIN32 -D_NDEBUG -D_WINDOWS

TARGET_MACHINE = X86

!ELSE IF "$(option)"== "4"
# The following is the setup for compilation option 4
CFGSET   =  TRUE

Configuration= Debug
Platform= Win32
CC_OPTION   = -D_WIN32 -D_DEBUG -D_WINDOWS

TARGET_MACHINE = X86

!ELSE IF "$(option)"=="5"

CFGSET=TRUE

!ENDIF

# Prompt usage
!IF "$(CFGSET)"== "FALSE"!MESSAGE Usage: nmake /f makefile.vc option=1or2or3or4or5   
!MESSAGE
!MESSAGE where is one of:!MESSAGE -  option=1- build  x64 release version
!MESSAGE -  option=2- build  x64 debug version
!MESSAGE -  option=3- build  x86 release version
!MESSAGE -  option=4- build  x86 debug version
!MESSAGE -  option=5- clear temp file and output file
!MESSAGE
!MESSAGE
!ERROR please choose a valid configuration instead"
!ENDIF

# If the compilation option is 5, execute cleaning operation
!IF "$(option)"=="5"
CLEAN:
    if exist "..\..\Intdir\Debug_x64\MyDepends" (del "..\..\Intdir\Debug_x64\MyDepends\*.obj") else echo Debug_x64 not exist
    if exist "..\..\Intdir\Debug_Win32\MyDepends" (del "..\..\Intdir\Debug_Win32\MyDepends\*.obj") else echo Debug_Win32 not exist
    if exist "..\..\Intdir\Release_x64\MyDepends" (del "..\..\Intdir\Release_x64\MyDepends\*.obj") else echo Release_x64 not exist
    if exist "..\..\Intdir\Release_Win32\MyDepends" (del "..\..\Intdir\Release_Win32\MyDepends\*.obj") else echo Release_Win32 not exist

!ELSE

# Set output folder and temporary folder
OUTDIR =..\..\OutDir\$(Configuration)_$(Platform)
INDIR =..\..\Intdir\$(Configuration)_$(Platform)\MyDepends

# Here add multiple outputs
all: $(INDIR) $(INDIR)\MyDepends.res $(OUTDIR) $(OUTDIR)\MyDepends.exe

# If the $(INDIR) folder does not exist, create it
$(INDIR) :
     if not exist "$(INDIR)" mkdir $(INDIR)

# If the $(OUTDIR) folder does not exist, create it
$(OUTDIR) :
    if not exist "$(OUTDIR)" mkdir $(OUTDIR)

# Run the compilation command
CC = $(CC_OPTION)

$(INDIR)\MessageProcess.obj:MessageProcess.c
    cl -c  $(CC) /Fo"$(INDIR)\" /Fd"$(INDIR)\" MessageProcess.c

$(INDIR)\MyDepends.obj:MyDepends.c $(INDIR)\MessageProcess.obj
    cl -c  $(CC) /Fo"$(INDIR)\" /Fd"$(INDIR)\" MyDepends.c

# Compile resource script
$(INDIR)\MyDepends.res:MyDepends.rc
    rc.exe /n /v /fo $(INDIR)\MyDepends.res MyDepends.rc

# Run the link command
$(OUTDIR)\MyDepends.exe: $(INDIR)\MyDepends.obj
    link /machine:$(TARGET_MACHINE) /NOLOGO /subsystem:WINDOWS /out:$(OUTDIR)\MyDepends.exe $(INDIR)\MyDepends.obj $(INDIR)\MessageProcess.obj $(INDIR)\MyDepends.res kernel32.lib user32.lib comdlg32.lib Shell32.lib
!ENDIF
It can be seen that nmake /f makefile.vc option=1 compiles the 64-bit release version, nmake /f makefile.vc option=2 compiles the 64-bit debug version, nmake /f makefile.vc option=3 compiles the 32-bit release version, nmake /f makefile.vc option=4 compiles the 32-bit debug version, and nmake /f makefile.vc option=5 performs the operation of cleaning output files.
What if the user outputs nmake /f makefile.vc option=6? Then the following prompt will appear:

Key Points for Writing Makefiles in Windows Environment

3. Notes for Writing Makefiles in Windows Environment

Originally, I wrote the command to clean the output like this:
!IF "$(RUN_CLEAN)"== "TRUE"if exist "../../Intdir/Debug_x64/TestLog"(del"..\..\Intdir\Debug_x64\TestLog\*.obj") else echo Debug_x64不存在if exist "../../Intdir/Debug_Win32/TestLog"(del"..\..\Intdir\Debug_Win32\TestLog\*.obj") else echo Debug_Win32不存在!ELSE
As a result, an error occurred, as shown in the following figure:

Key Points for Writing Makefiles in Windows Environment

It needs to be modified to:

!IF "$(RUN_CLEAN)"== "TRUE"
CLEAN:
    if exist "..\..\Intdir\Debug_x64\TestLog"(del"..\..\Intdir\Debug_x64\TestLog\*.obj") else echo Debug_x64 not exist
    if exist "..\..\Intdir\Debug_Win32\TestLog"(del"..\..\Intdir\Debug_Win32\TestLog\*.obj") else echo Debug_Win32 not exist
    if exist "..\..\Intdir\Release_x64\TestLog"(del"..\..\Intdir\Release_x64\TestLog\*.obj") else echo Release_x64 not exist
    if exist "..\..\Intdir\Release_Win32\TestLog"(del"..\..\Intdir\Release_Win32\TestLog\*.obj") else echo Release_Win32 not exist

!ELSE
The key points are three:

1. A block name must be added, such as CLEAN:

2. There should be no blank lines between CLEAN: and if exist.

3. At least one tab must be inserted before if exist.

【Author of this article】

Author: Zhu Jincan, graduated with a bachelor’s degree in GIS from CUG (Wuhan). Currently engaged in software development and team management work in Beijing. He has won the second prize of the Nonferrous Metal Industry Science and Technology Progress Award. Personal blog: https://blog.csdn.net/clever101. WeChat public account: HiProgrammer

Recommended Reading

(Clicking the title will jump to read)

Introduction to Makefile

Microsoft’s new programming font is open-source, and one can accidentally curse while using it

Did this article help you? Please share it with more people

Follow ‘CPP developers’ and star it to enhance C/C++ skills

Key Points for Writing Makefiles in Windows Environment

Good article, I’m reading ❤️

Leave a Comment