Makefile troubleshooting (and proper practice)
Posted: Mon Oct 10, 2016 3:44 am
Hello everyone, I think this should be the correct place to post this, but let me know if it belongs somewhere else. I use a pretty standard linux-style build system to compile my OS kernel (GNU cc, as, make, etc.) which includes a makefile for the kernel. I was having a bit of trouble with make, and was wondering if people know why this is happening or what I can do to fix the problem. For reference, the full makefile is here.
My makefile isn't too complex for the most part, the top half of it contains mostly definitions of stuff like paths, tools, and command options. In order to save myself having to add a new recipe every time I add a new file, the makefile "generates" a list of files it needs to build using the wildcard and patsubst commands in make, here's a snippet of how I do that:
These definitions search a directory for all the C files, and then generate a list of all the object files that those C files need to be compiled to. There's two lists, one for stuff in the architecture dependent sources, and one for stuff in the architecture independent sources.
Once these lists of files are created, I use some recipes to compile them, like so:
This shouldn't be super complicated. The final link of the kernel requires all the files in the object file list, so this recipe should match those and compile them all. The process for assembly files is pretty similar, it just uses GNU as instead of gcc. Again, there's a link to the full makefile at the top if you want to see the whole thing.
So, if you have a i686-elf cross compiler, you might want to try actually downloading the repo and running "make bin/i686-elf-kernel.bin" to get it to compile just the kernel binary. Hopefully it should work the first time you try it (I can't guarantee it, I've only tested it on my own PCs), but if you run it again, you'll notice that instead of saying "nothing to be done", it goes ahead and makes everything again. This is unintended behaviour from my point of view, and while it's fine to recompile everything now when there's only about 15-20 object files to do, it's pretty quick. But later on, if I had 2000 object files to make, this would be awfully slow, and kind of defeat the purpose of using make. So, that's my first question, why is it making everything again? Is something weird happening with last modified time stamps, or is there a critical flaw in the design of my makefile. I think that it probably has something to do with the way I generate lists of files to be built, but I can't think of another way to do it, and from my understanding of how make works, it should be properly excluding files with no changes. Which leads into my second question.
My second question is, what's the considered "Good practice" when I have a project like this. Specifically, I don't want to edit a "List of files to be built" every time I add a new file to my project, that was how I did it before, and it got very tedious, very quickly, and was highly prone to mistakes (I would spend ages looking for typos and bugs when the linker couldn't find stuff, only to realise it wasn't being compiled in the first place). What are some other methods of coming up with dependencies at run-time? My current method works in terms of getting the job done, but it seems to have problems figuring out what work actually needs to be done. I'm happy to just hear about the way other people's makefiles work as well, it would be interesting to see how other people do their build systems.
- Mikumiku747
My makefile isn't too complex for the most part, the top half of it contains mostly definitions of stuff like paths, tools, and command options. In order to save myself having to add a new recipe every time I add a new file, the makefile "generates" a list of files it needs to build using the wildcard and patsubst commands in make, here's a snippet of how I do that:
Code: Select all
KERN_C_ARCH_SOURCES := $(wildcard $(KERN_ARCH_SRC)/*.c)
KERN_C_ALL_SOURCES := $(wildcard $(KERN_ALL_SRC)/*.c)
KERN_C_ARCH_OBJS := $(patsubst $(KERN_ARCH_SRC)/%.c, obj/%.o, $(KERN_C_ARCH_SOURCES))
KERN_C_ALL_OBJS := $(patsubst $(KERN_ALL_SRC)/%.c, obj/%.o, $(KERN_C_ALL_SOURCES))
Once these lists of files are created, I use some recipes to compile them, like so:
Code: Select all
obj/%.o: $(KERN_ARCH_SRC)/%.c obj
$(KERN_CC) $(KERN_CC_OPTS) $(KERN_C_INCLS) -c $< -o $@
obj/%.o: $(KERN_ALL_SRC)/%.c obj
$(KERN_CC) $(KERN_CC_OPTS) $(KERN_C_INCLS) -c $< -o $@
So, if you have a i686-elf cross compiler, you might want to try actually downloading the repo and running "make bin/i686-elf-kernel.bin" to get it to compile just the kernel binary. Hopefully it should work the first time you try it (I can't guarantee it, I've only tested it on my own PCs), but if you run it again, you'll notice that instead of saying "nothing to be done", it goes ahead and makes everything again. This is unintended behaviour from my point of view, and while it's fine to recompile everything now when there's only about 15-20 object files to do, it's pretty quick. But later on, if I had 2000 object files to make, this would be awfully slow, and kind of defeat the purpose of using make. So, that's my first question, why is it making everything again? Is something weird happening with last modified time stamps, or is there a critical flaw in the design of my makefile. I think that it probably has something to do with the way I generate lists of files to be built, but I can't think of another way to do it, and from my understanding of how make works, it should be properly excluding files with no changes. Which leads into my second question.
My second question is, what's the considered "Good practice" when I have a project like this. Specifically, I don't want to edit a "List of files to be built" every time I add a new file to my project, that was how I did it before, and it got very tedious, very quickly, and was highly prone to mistakes (I would spend ages looking for typos and bugs when the linker couldn't find stuff, only to realise it wasn't being compiled in the first place). What are some other methods of coming up with dependencies at run-time? My current method works in terms of getting the job done, but it seems to have problems figuring out what work actually needs to be done. I'm happy to just hear about the way other people's makefiles work as well, it would be interesting to see how other people do their build systems.
- Mikumiku747