Embed With Elliot: March Makefile Madness

The make tool turns the big 4-0 next month , and we thought we’d start up the festivities early. In a two-part series, I’ll cover some of the make background that I think is particularly useful, and then focus on microcontroller-specific applications. If you’re still cut-and-pasting a general purpose makefile to run your toolchain, hopefully you’ll get enough insight here to start rolling your own. It can be a lot simpler than it appears!

Just as soon as the C programming language was invented, and projects started to get a little bit bigger than a “hello world”, it became obvious that some tool was needed to organize and automate compilation. After all, if you’ve got a program that’s spread over a number of files, modules, or libraries, it’s a hassle to have to re-compile them all any time you make a change to just a single section of code. If some parts haven’t changed, you’re just wasting time by re-compiling them. But who can keep track of all of this? Make can!

In fact, make can do just about anything. It’s very good at handling generic rules to resolve complex dependency chains among files. It knows which files have changed along the way, and only re-builds whatever is necessary to get the job done. And it’s highly extensible — you can train make , via carefully crafted makefiles, to do essentially anything. I once used make to automatically e-mail out grade updates only to those students whose grades had changed.

In this age of IDEs and GUI-driven development, there’s something to be said for writing code with an editor, a compiler, and a makefile. But makefiles can be dauntingly complex. Have a look at the “standard” AVR makefile , for instance, written by Eric Weddington and Jörg Wunsch. I used that file for countless projects over probably ten years without seriously reading through and understanding it all — it just does too much. Similarly, [sudar]’s makefiles will compile Arduino code for you without touching the Arduino IDE . That’s great when you need it, but it weighs in at 1,500 lines of code.

A one-size-fits-all magic makefile isn’t a bad thing, unless you want to learn about how the make system works. In that case, all of the generalities, special cases, and gilded lilies just get in the way. And I’ll claim that for many projects, writing a quick project-specific makefile is a good practice. It keeps things simple, and helps make sure that you know what’s actually going on.

So we’ll start here with an absolutely minimal makefile, and work our way up toward something that’s more reasonable for most projects. Along the way, I’ll work through a tiny subset of the tools that make gives you. Why? Because most of the features are for such special (or general) cases that you’ll almost never need them in practice. My goal here is to get you understanding the make system well enough that you don’t need an IDE to get your coding done.

Sometimes the Coolest Makefile is No Makefile at All

So what’s the most minimal makefile that you can use to compile your project? Would you believe none at all? The make system has a tremendous amount of functionality built into it. I want you to think of a makefile as expanding or customizing that inherent functionality.

To show you what I mean, here’s “hello world” in C, written for my desktop computer:

#include <stdio.h>  int main(void) {     printf("hello world!/n");        return 0; }

Type that in, save it in a file called hello.c and you’ve started programming. Next, you’ll want to compile and run it. Assuming that you’re using gcc as your compiler, you could type something like gcc hello.c -o hello and then run the resulting program ( hello on a Unix/Mac, hello.exe on Windows) when you’re done.

Honestly, it wasn’t all that much to type, but how much cooler is typing simply make hello ? Lots cooler. But more to the point, it demonstrates that make already knows how to do some simple stuff, like compile C code into an executable program. Even with no makefile present, typing make hello will run the following command for you: cc hello.c -o hello — almost exactly what we did by hand above.

Rules all the Way Down

How does it do this? Make has pre-defined rules for making files of different types from other files, and it’s pretty clever about chaining these rules up to make your project work out. So, for instance, if you need to build hello.hex to flash into your microcontroller, and there’s a rule to make hello.hex from hello.elf , and a rule to make hello.elf from hello.o (and maybe some other object files), and a rule to make hello.o from hello.c , then make will chain them all together to get the job done.

As an example, let’s write out an explicit rule so we can see how it works:

hello: hello.c     gcc hello.c -o hello

The first line tells make that our executable hello depends on hello.c . The tab-indented line that follows is the rule that tells make how to get from the dependency to the target. Indeed, if you wrote this into your makefile, you’d be just about where we started with no makefile.

Makefiles start to get interesting as the number of sub-parts of the project increase. Say we’ve split our code into two modules now, and we want to compile them together. One way to handle that would be to include the dependencies explicitly:

hello: hello.c extras.c     gcc hello.c extras.c -o hello

But you can see that as we include more and more source files, we’ll have a lot more typing to do. We could define a variable to hold all of the source files:

SRC=hello.c extras.c hello: $(SRC)     gcc $(SRC) -o hello

Our target, hello , depends on all of the source files that are listed in the SRC variable, and all of those are included in the compilation command. That’s a decent solution, and fairly readable, which is a virtue. But as we demonstrated when we used no makefiles whatsoever, make already has built-in rules to handle simple cases like this. We should use them.

Customizing the Default Rules

Make ‘s default rules are called “implicit rules” in the docs, and now that we understand an explicit rule, let’s look into the implicit rules a little, and see about how to tweak them. For instance, the implicit rule that turns a C file into an executable looks like this: .

%: %.c     $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LOADLIBES) $(LDLIBS) -o $@

(You can find this by running make -p and looking through the resulting 1,500 lines.)

There’s a lot going on here, but the basic format of the rule is the same: a target (%) depends on a C file (or files) and then there’s a tab-indented rule that says how to get there. Only here, the % is a wildcard, the special variables $^ and $@ refer to the dependencies and the target respectively, and everything else that you’d likely want to change is included via those “implicit variable” names that appear in all caps.

If you’d actually tried to run the makefile -less version in the first section on Windows, it might have failed. Why? Because my Windows system doesn’t call the compiler “cc” — the default value for the variable CC — but rather “gcc”. We can fix that up by defining the CC variable to our compiler of choice.

Additionally, I like to compile with all possible compiler warnings enabled by default, passing the compiler the -Wall flag. That’s exactly what the CFLAGS variable is for. To always compile with warnings on, we simply define CFLAGS=-Wall and we’re set. Finally, the way the implicit rule is defined supports multiple C-file dependencies, so we might as well list all of the files needed for our project in a single dependency statement.

Our improved makefile , specifying a particular compiler, two dependencies for our compiled target, and the compiler warning flag, looks like this:

CC=gcc CFLAGS=-Wall  hello: hello.c extras.c

Notice that we didn’t have to specify a rule to go with the target/dependencies because there’s already an implicit rule that matches this pattern. Since the hello target it the first explicit target in the file, it gets made automatically, and we don’t even need to type “hello” every time. The result is that we can simply type make and it runs gcc -Wall hello.c extras.c -o hello .

For further customization, all of the explicit variables are documented , so you can read through that list if you’re curious about whether an option belongs in LOADLIBES or LDLIBS . And we’ll be working with these more next time, so stay tuned.

Getting Fancy

Notice how far we’ve gotten with just a three-line makefile . You’ve been introduced to the implicit rules, and gotten a bit of the flavor of how to override the implicit variables that go along with them to customize the compilation.

Make gets really interesting when cross-compiling and adding all sorts of custom targets to drive an embedded programming toolchain. We’ll get into those in the next installment, with minimal AVR and STM32 ARM Cortex makefile examples. The goal is going to be maintaining the same sort of readability, but as Albert Einstein said, we’ll be aiming for “as simple as possible, but no simpler.”

Windows Postscript

If you don’t have make installed on your Windows system yet, I had success with mysys2 . Follow the initial install instructions through the update, and then pacman --needed -Su base-devel mingw-w64-i686-toolchain will get you everything you need to make and compile in C, C++, and a few other languages. The console applications from the mingw package have all the right PATH variables set so that you can just type make and it’ll run.

If anyone else has a favorite or streamlined way of getting a compiler and make up and running on Windows, post up in the comments.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Embed With Elliot: March Makefile Madness

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址