I would like to build a C-project for my microcontroller with the GNU make tool. I would like to do it in a clean way, such that my source code is not cluttered with object files and other stuff after the build. So imagine that I have a project folder, called "myProject" with two folders in it:
- myProject
|
|---+ source
|
'---+ build
The build folder only contains a makefile. The figure below shows what should happen when I run the GNU make tool:
So GNU make should create an object file for each .c source file it can find in the source folder. The object files should be structured in a directory tree that is similar to the structure in the source folder.
GNU make should also make a .d dependency file (in fact, a dependency file is some sort of makefile itself) for each .c source file. The dependency file is described in the GNU make manual chapter 4.14 "Generating Prerequisites Automatically":
For each source file name.c there is a makefile name.d which lists what files the object file name.o depends on.
From the following Stackoverflow question About the GNU make dependency files *.d, I learned that adding the options -MMD
and -MP
to the CFLAGS
of the GNU gcc compiler can help to automate that.
So now comes the question. Has anyone a sample makefile that performs such out-of-source build? Or some good advices on how to get started?
I'm pretty sure that most people who have written such a makefile, are Linux-people. But the microcontroller project should build also on a Windows machine. Anyway, even if your makefile is Linux-only, it provides a good starting point ;-)
PS: I would like to avoid extra tools like CMake, Autotools, or anything that has to do with an IDE. Just pure GNU make.
I would be very grateful :-)
Updating the dependency files
Please have a look at this question: What is the exact chain of events when GNU make updates the .d files?
Here's a basic one I use all the time, it's pretty much a skeleton as it is but works perfectly fine for simple projects. For more complex projects it certainly needs to be adapted, but I always use this one as a starting point.
I would avoid manipulating Makefile directly, and use CMake instead. Just describe your source files in CMakeLists.txt, as below:
Create file MyProject/source/CMakeLists.txt containing;
Under MyProject/build, run
You'll get a Makefile now. To build, under the same build/ directory,
You may also want to switch to a lightning fast build tool, ninja, simply by adding a switch as following.
This fairly minimal makefile should do the trick:
The main tricky part is ensuring that
FolderA
andFolderB
exist in the build directory bfore trying to run the compiler that will write into them. The above code will work sequential for builds, but might fail with-j2
the first time it is run, as the compiler in one thread might try to open an output file before the other thread creates the directory. Its also somewhat unclean. Usually with GNU tools you have a configure script that will create those directories (and the makefile) for you before you even try to run make. autoconf and automake can build that for you.An alternate way that should work for parallel builds would be to redefine the standard rule for compiling C files:
Which has the disadvantage that you'll also need to redefine the builtin rules for any other kind of sourcefile you want to compile
Here's the Makefile I've added to the documentation (currently in review so I'll post it here) :
Main features
C
sources in specified foldersgcc
: Build only what is necessaryUnix
andDOS
systemsGNU Make
How to use this Makefile
To adapt this Makefile to your project you have to :
TARGET
variable to match your target nameSources
andBuild
folders inSOURCEDIR
andBUILDDIR
make all VERBOSE=FALSE
)DIRS
to match your sources and build foldersIn this Makefile
Folder0
,Folder1
andFolder2
are the equivalent to yourFolderA
,FolderB
andFolderC
.Note that I have not had the opportunity to test it on a Unix system at the moment but it works correctly on Windows.
Explanation of a few tricky parts :
Ignoring Windows mkdir errors
This has two effects : The first one,
2>NUL
is to redirect the error output to NUL, so as it does not comes in the console.The second one,
|| true
prevents the command from rising the error level. This is Windows stuff unrelated with the Makefile, it's here because Windows'mkdir
command rises the error level if we try to create an already-existing folder, whereas we don't really care, if it does exist that's fine. The common solution is to use theif not exist
structure, but that's not UNIX-compatible so even if it's tricky, I consider my solution more clear.Creation of OBJS containing all object files with their correct path
Here we want OBJS to contain all the object files with their paths, and we already have SOURCES which contains all the source files with their paths.
$(SOURCES:.c=.o)
changes *.c in *.o for all sources, but the path is still the one of the sources.$(subst $(SOURCEDIR),$(BUILDDIR), ...)
will simply subtract the whole source path with the build path, so we finally have a variable that contains the .o files with their paths.Dealing with Windows and Unix-style path separators
This only exist to allow the Makefile to work on Unix and Windows, since Windows uses backslashes in path whereas everyone else uses slashes.
SEP=\\
Here the double backslash is used to escape the backslash character, whichmake
usually treats as an "ignore newline character" to allow writing on multiple lines.PSEP = $(strip $(SEP))
This will remove the space char of theSEP
variable, which has been added automatically.Automatic generation of rules for each target folder
That's maybe the trick that is the most related with your usecase. It's a rule template that can be generated with
$(eval $(call generateRules, param))
whereparam
is what you can find in the template as$(1)
. This will basically fill the Makefile with rules like this for each target folder :