Last post I said I was done, for now at least, messing with makefiles. Well, I figured I needed to make sure things really were working as reasonably as I thought they were. So, I’ve decided to see if I can compile and run the code for the Week 4 (Memory) problem set: Filter. filter takes an input image file (only BMP for the purposes of the exercise) and outputs/writes a filtered image file. There is only a small number of simple filters. I had it working under the CS50 IDE.

It involves two header files, and two code files — pretty much the most complicated file structure for any of the problem sets done in C. I’d like to get it compiling in VSCode on a Windows 10 PC. You know:

“I know you’ve heard it a thousand times before. But it’s true - hard work pays off. If you want to be good, you have to practice, practice, practice. If you don’t love something, then don’t do it.”
— Ray Bradbury

And, if I feel I need more practice, I may try to locally compile one of the other C exercises from the course.

Project Structure

So a new folder, rek_fltr. Downloaded all the necessary files from my CS50 IDE account. And my directory structure looks something like this:

learn/edX
├── cs50/
│   │
│   ├── include/
│   │   ├── bmp.h
│   │   ├── cs50.h
│   │   └── helpers.h 
│   │
│   │─── lib/
│   │   └── cs50.c 
│   │
│   │─── rek_1/
│   │    ├── obj/
│   │    │   └── *.o
│   │    ├── Makefile
│   │    └── rek_hello.c
│   │
│   │─── rek_fltr/
│   │    ├── helpers/
│   │    │   └── helpers.c
│   │    │
│   │    ├── images/
│   │    │   └── *.bmp (images provided for use with the exercise)
│   │    │
│   │    ├── obj/
│   │    │   └── *.o (eventually)
│   │    │
│   │    ├── filter.c
│   │    └── Makefile
│   │
│   ├── .gitignore
│   ├── cs50.code-workspace
│   ├── rek_notes.txt
│   └── rek_recent_web_pages.txt
...

Won’t be using the CS50 library for this one. But, I was using the markdown for a previous post to get the above diagram, so…

I decided to continue putting header files in their own directory. And because helpers is essentially a library for filter.c I put it in its own directory as well. But, because it is so closely linked with the code for filter, I made it a sub-directory rather than a sibling.

Makefile

Okay, the Makefile is the nitty gritty for getting this to work, as I know the code did work on the CS50 IDE. It will, I expect, be very similar to the Makefile in the previous post. So, we’ll start with some of the definitions used in that one.

Before starting on the makefile, I made sure to open VSCode in the rek_fltr folder. Not my default CS50 workspace. Recall that the Makefile Tools VSCode extension appears to use the default workspace folder for all its file paths/references.

No compiling or linking just yet. Did add the “clean” target so that I could do a test run. Note the -iquote flag. The code files use the quote syntax for the include for helper.h. The standard C libraries are included using the angle bracket syntax.

In fact, cs50.c also used the quote syntax for its include of cs50.h. But, I didn’t research it at the time and simply changed it to the bracket syntax. Should probably go back and return it to its original syntax and update the Makefile. Perhaps another day.

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>

#include "helpers.h"

Okay, here’s the makefile so far:

# Specify compiler
CC = E:\appDev\TDM-GCC-64\bin\gcc
# Specify linker
LINK = E:\appDev\TDM-GCC-64\bin\gcc

LINK_TARGET = filter.exe

# compiler flags
CFLAGS = -Wall -Wextra -Werror -pedantic -std=c11

# include dir
IDIR = ..\include
# project library dir
LDIR = helpers
# dir for object files to be linked into executable
ODIR = obj

# flags for include and library locations
IFLG = -I $(IDIR)
IQFL = -iquote $(IDIR)
LFLG = -L $(LDIR)

# let's define our header file dependencies
_DEPS = bmp.h helpers.h
DEPS = $(patsubst %,$(IDIR)\\%,$(_DEPS))

# and any libraries we need
_LIBS = helpers.c
LIBS = $(patsubst %,$(LDIR)\\%,$(_LIBS))

# create a list, with path, of the object files we will be needing
_OBJ = filter.o helpers.o
OBJS = $(patsubst %,$(ODIR)\\%,$(_OBJ))

.PHONY : clean

# get rid fo the object files and executables for clean start
clean:
	del $(ODIR)\*.o
	del $(LINK_TARGET)

As soon as I saved the file and clicked on the Makefile Tools icon in the Activity bar, the tool checked the makefile, apparently executed a dry-run, and such. Looked like it was happy with what it saw. So, I set the target to “clean” and ran the makefile. And:

Building the current target. Command: E:\appDev\TDM-GCC-64\bin\mingw32-make.exe clean
del obj\*.o
Could Not Find r:\learn\edX\cs50\rek_fltr\obj\*.o
del filter.exe
Could Not Find r:\learn\edX\cs50\rek_fltr\filter.exe
Target clean built successfully.

Nothing to delete so, looks to be working. Okay, let’s build some object files. I added the following to the makefile.

# compile the object files, two in this case
$(ODIR)\filter.o : filter.c $(DEPS)
# print some debug stuff
	@echo $(DEPS)
	@echo $(LIBS)
	@echo $(OBJS)
	@echo $@ $<
	$(CC) -c -o $@ $< $(IFLG) $(IQFL)

$(ODIR)\helpers.o : $(LIBS) $(DEPS)
	@echo $@ $<
	$(CC) -c -o $@ $< $(IFLG) $(IQFL)

Now, since I don’t have an all target defined, I am just going to select one of the above as the target and run the makefile from VSCode. I selected obj\helpers.o, and:

Building the current target. Command: E:\appDev\TDM-GCC-64\bin\mingw32-make.exe obj\helpers.o
obj\helpers.o helpers\helpers.c
E:\appDev\TDM-GCC-64\bin\gcc -c -o obj\helpers.o helpers\helpers.c -I ..\include -iquote ..\include
Target obj\helpers.o built successfully.

And, checking the obj\ directory, helpers.o was definitely there. So, let’s add something to allow me to compile both object files with one target. I moved some of the print statements from under the filter.o target under the app target so that there would be something for it to run. Since it has OBJS as a dependency, all those targets should be run — i.e. compiled into object files. Here’s the relevant modified portion of the makefile:

...
OBJS = $(patsubst %,$(ODIR)\\%,$(_OBJ))

# Build all
.PHONY : all

all : app

# compile the object files, two in this case
$(ODIR)\filter.o : filter.c $(DEPS)
# print some debug stuff
	@echo $@ $<
	$(CC) -c -o $@ $< $(IFLG) $(IQFL)

$(ODIR)\helpers.o : $(LIBS) $(DEPS)
	@echo $@ $<
	$(CC) -c -o $@ $< $(IFLG) $(IQFL)

app : $(OBJS)
	@echo $(DEPS)
	@echo $(LIBS)
	@echo $(OBJS)

.PHONY : clean
...

And?

Building the current target. Command: E:\appDev\TDM-GCC-64\bin\mingw32-make.exe clean all
del obj\*.o
del filter.exe
Could Not Find r:\learn\edX\cs50\rek_fltr\filter.exe
obj\filter.o filter.c
E:\appDev\TDM-GCC-64\bin\gcc -c -o obj\filter.o filter.c -I ..\include -iquote ..\include
obj\helpers.o helpers\helpers.c
E:\appDev\TDM-GCC-64\bin\gcc -c -o obj\helpers.o helpers\helpers.c -I ..\include -iquote ..\include
..\include\bmp.h ..\include\helpers.h
helpers\helpers.c
obj\filter.o obj\helpers.o
Target all built successfully.

And, the two object files were present in the obj\ directory. filter.o at 4 KB and helpers.o at 7 KB. So, let’s move onto building filter.exe.

Compile and Test

Under the app target I added the instruction to link and generate the executable.

app : $(OBJS)
	@echo $(DEPS)
	@echo $(LIBS)
	@echo $(OBJS)
	$(CC) -o $(LINK_TARGET) $^ $(CFLAGS) $(DEPS) $(IFLG)

Since all was alredy selected as the target, I used the more actions menu to select Build clean. And:

Building the current target. Command: E:\appDev\TDM-GCC-64\bin\mingw32-make.exe clean all
del obj\*.o
del filter.exe
obj\filter.o filter.c
E:\appDev\TDM-GCC-64\bin\gcc -c -o obj\filter.o filter.c -I ..\include -iquote ..\include
obj\helpers.o helpers\helpers.c
E:\appDev\TDM-GCC-64\bin\gcc -c -o obj\helpers.o helpers\helpers.c -I ..\include -iquote ..\include
..\include\bmp.h ..\include\helpers.h
helpers\helpers.c
obj\filter.o obj\helpers.o
E:\appDev\TDM-GCC-64\bin\gcc -o filter.exe obj\filter.o obj\helpers.o -Wall -Wextra -Werror -pedantic -std=c11 ..\include\bmp.h ..\include\helpers.h -I ..\include
Target all built successfully.

And, now filter.exe was in my working directory. So, I moved to VSCodes Terminal tab and executed it.

R:\learn\edX\cs50\rek_fltr>.\filter.exe
Usage: filter [flag] infile outfile

R:\learn\edX\cs50\rek_fltr>filter -g yard.bmp test_g.bmp
Could not open yard.bmp.

R:\learn\edX\cs50\rek_fltr>filter -g images\yard.bmp test_g.bmp

Seemed to work and test_g.bmp was present in the directory. But, I couldn’t get any of my applications to open it. Complaints about it not being a valid format. Modified the code to open files in binary mode, recompiled, and bingo! Windows versus Linux? I am guessing the CS50 IDE is Linux/Unix based.

Until Next Time

You know, I am feeling reasonably good about this whole thing. So, am going to call it quits for this one.

See you next time.

Resources