0

Since sdcc have some limitations, like compiling one file at a time, I've tried hard to write a Makefile to perfect the automation of MCS-51 development, which have some requirements:

  1. Source file (.c) expect main.c are stored in ProjectFolder/Sources/, while main.c are stored at the root of project folder.
  2. Headers are stored in ProjectFolder/Includes/.
  3. Outputs through compiling, linking and locating should be stored at ProjectFolder/Builds/
  4. Makefile should be smart enough to find all source files, instead of type their file name by hand.
  5. Makefile should be smart enough to if there are some files in Sources/, or there's only main.c in the project.

The file structure can be depicted like:

Project Folder
|
|- Sources
|  |
|  |(some source files, but OPTIONAL)
|
|- Includes
|  |
|  |(some headers, but OPTIONAL)
|
|- Builds
|  |
|  |(some .rel .o .hex files. OUTPUT here)
|
|- main.c
|
|- Makefile

Here's my solution but still have a problem. It cannot be used for project only have one file main.c which means no source file in Sources/.

INCLUDES = Includes/
SOURCES = Sources/
BUILDS = Builds/
CC = sdcc
CFLAGS = -o $(BUILDS)
LOADER = stcgal
LOADER_FLAGS = -P stc89

$(BUILDS)main.ihx: main.c $(BUILDS)main.rel
#   Link
    @$(CC) main.c $(shell find $(BUILDS) -name "*.rel" -not -name "main.rel" -maxdepth 1) $(CFLAGS)
    @echo Link & Locate Succeeded

$(BUILDS)main.rel: $(SOURCES) $(BUILDS)
#   Compile
    @for f in $(shell ls $(SOURCES)*.c) ; do \
        $(CC) -c $${f} $(CFLAGS) ; \
    done
    @echo Compile Succeeded

$(SOURCES):
    @mkdir $(SOURCES)

$(BUILDS):
    @mkdir $(BUILDS)

clean:
#   Remove all files in build folder
    @rm $(BUILDS)*
    @echo Build Folder Cleaned

load: $(BUILDS)main.ihx
#   Load data to MCU via USB port
    @$(LOADER) $(LOADER_FLAGS) -p $(shell ls /dev/tty.usbserial*) $(BUILDS)main.ihx
  • You might want to read make's manual to find more built-in functions, like `wildcard`, or other features, like `VPATH`. – the busybee Aug 31 '22 at 13:41
  • 1
    Writing your makefile like this, where one recipe builds all the object files, means you are almost not even using make at all. You might as well write a shell script. Whenever any single source file or object file changes, ALL the source files will be rebuilt even the ones that didn't change. The way to write a makefile is to create a recipe that builds _one source file_ and generates _one output file_. Then you let make decide when to run it based in prerequisites. – MadScientist Aug 31 '22 at 16:51
  • @MadScientist But the problem is that how can I do it automatically. Since it's annoying to change Makefile every time when I want to add a new source file, is it possible to use this "one source, one output" pattern while fulfill my automation requirements. –  Sep 01 '22 at 00:13
  • 2
    Well, I've never understood this "annoyance". I mean, realistically how many source files do you plan to add and is it REALLY a problem to take an extra 4 seconds to add the file to the makefile? In my build systems I always explicitly list all the source files because otherwise you'll get weird build failures if you happen to have a temporary test file or something lying around in your directory. But as noted by the busybee, you can use the `wildcard` function to ask make to get a list of all the source files in your directories. – MadScientist Sep 01 '22 at 16:00

1 Answers1

0

Let's try something. First note that I've not looked at the load target.

Let's start with the same definition as you:

INCLUDES = Includes/
SOURCES = Sources/
BUILDS = Builds/
CC = sdcc

We need a variable with the source files from Sources. GNU Make has a wildcard functions which does the same thing as your find. See that I'm using := to have an immediate expansion of the value, so the wildcard will not be executed several times.

SRCFILES := $(wildcard $(SOURCES)*.c) 

Now a variable with the .rel files. It is build from main.rel and the SRCFILES value:

RELFILES := $(BUILDS)main.rel $(SRCFILES:$(SOURCES)%.c=$(BUILDS)%.rel)

Let's define another variable with the flag to pass so the Includes directory is searched:

CPPFLAGS = -I$(INCLUDES)

Now we can define pattern rules to describe how to build .rel files from .c files. I'm using an order-only prerequisite for the build directory:

$(BUILDS)%.rel: $(SOURCES)%.c | $(BUILDS)
        $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ -c $<

$(BUILDS)%.rel: %.c | $(BUILDS)
        $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<

Let's define some usability targets:

.PHONY: all clean

all: $(BUILDS)main.ihx

clean:
        rm $(BUILDS)*

And finally define how to build the targets which aren't handled by the pattern rules:

$(BUILDS)main.ihx: $(RELFILES) | $(BUILDS)
        $(CC) $(LDFLAGS) -o $@ $^ $(LOADLIBES) $(LDLIBS)

$(BUILDS):
        mkdir $(BUILDS)

I've used a few variables (CC, CPPFLAGS, CFLAGS, LDFLAGS, LOADLIBES, LDLIBS) in the same way as they are used by the built-in rules of GNU Make.

I've kept your makefile behavior. There are good reasons to have Makefiles targeting to build in the current directory. Explaining them and modifying the Makefile for that is out of scope for this answer, you may look at MadScientist's GNU Make White Papers and the GNU Make Manual.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143