0

When I run make tests, I get the following output:

cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG  build/liblcthw.a    tests/list_tests.c  -ldl -o tests/list_tests
In file included from tests/list_tests.c:1:
tests/list_tests.c: In function ‘main’:
/usr/bin/ld: /tmp/ccBX85Eg.o: in function `test_create':
/home/taimoorzaeem/Documents/cprogs/lcthw/liblcthw/tests/list_tests.c:11: undefined reference to `List_create'
/usr/bin/ld: /tmp/ccBX85Eg.o: in function `test_destroy':
/home/taimoorzaeem/Documents/cprogs/lcthw/liblcthw/tests/list_tests.c:19: undefined reference to `List_clear_destroy'
--- a lot of similar errors

It turns out, if a run the same command like cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG tests/list_tests.c build/liblcthw.a -ldl -o tests/list_tests, it works perfectly fine.

This seems like an issue with specifying .a files in Makefiles. What changes do I need to make in my Makefile to run the make tests like the mentioned command. Is there a recommended way to specify .a files in Makefile?

My directory structure looks like this:

.
├── bin
├── build
│   ├── liblcthw.a
│   └── liblcthw.so
├── LICENSE
├── Makefile
├── README.md
├── src
│   └── lcthw
│       ├── dbg.h
│       ├── list.c
│       ├── list.h
│       └── list.o
└── tests
    ├── list_tests.c
    ├── minunit.h
    └── runtests.sh

This is how my Makefile looks:

CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)
LIBS=-ldl $(OPTLIBS)
LDLIBS=-ldl
PREFIX?=/usr/local

SOURCES=$(wildcard src/**/*.c src/*.c)
OBJECTS=$(patsubst %.c, %.o, $(SOURCES))

TEST_SRC=$(wildcard tests/*_tests.c)
TESTS=$(patsubst %.c,%,$(TEST_SRC))

TARGET=build/liblcthw.a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))

#The target build
all: $(TARGET) $(SO_TARGET) tests

dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all

$(TARGET): CFLAGS += -fPIC
$(TARGET): build $(OBJECTS)
    ar rcs $@ $(OBJECTS)
    ranlib $@

$(SO_TARGET): $(TARGET) $(OBJECTS)
    $(CC) -shared -o $@ $(OBJECTS)

build:
    @mkdir -p build
    @mkdir -p bin

# The Unit Tests
.PHONY: tests
tests: CFLAGS += $(TARGET)
tests: $(TESTS)
    sh ./tests/runtests.sh

valgrind:
    VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE)

# The cleaner
clean:
    rm -rf build $(OBJECTS) $(TESTS)
    rm -f tests/tests.log
    find . -name "*.gc*" -exec rm {} \;
    rm -rf `find . -name "*.dSYM" -print`

# The install
install: all
    install -d $(DESTDIR)/$(PREFIX)/lib/
    install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/

# The checker
BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)'
check:
    @echo Files with potentially dangerous functions
    @egrep $(BADFUNCS) $(SOURCES) || true
Taimoor Zaeem
  • 190
  • 1
  • 12
  • Not sure if this is relevant, but you are not using the same command. You swapped the .c and .a files in your command. – Gerhardh Aug 10 '22 at 11:32
  • 3
    `CFLAGS += $(TARGET)` You should not be adding link libs to `CFLAGS`. Doing that results in the link lib being placed before the source file that needs it on the build command line. Add it to `LDLIBS` instead. – kaylum Aug 10 '22 at 11:35
  • Does this answer your question? [What is an undefined reference/unresolved external symbol error and how do I fix it?](https://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix) – the busybee Aug 10 '22 at 12:06
  • @thebusybee that linked question does not address how to add archives/libraries in makefle. The OP seems to know that the archive must be added but struggles with the way how to do it. – Gerhardh Aug 10 '22 at 12:25
  • 1
    @Gerhardh Well, I might be naive to expect that a developer is able to compare both command lines, to read and understand the answers of the linked issue as an important hint, to conclude that the sequence of command line arguments is important, and then to know how to correct the Makefile. Apparently this is too much to expect. :-D Never mind. – the busybee Aug 10 '22 at 13:57
  • Yes, it is too much to expect. Thank you @thebusybee. – Taimoor Zaeem Aug 10 '22 at 15:02

1 Answers1

2

The answer to your question is mentioned in the comments but just to be very specific: the difference between your working and not-working versions is the order of the arguments. This has nothing to do with make or makefiles specifically: if you copy the identical command that make ran and paste it at your shell prompt, you'll get the same error so it's clearly not make-related.

The order of arguments to the compiler matters (not always, not for all options, but often). When you link code you always want all your object files (.o) to come first, then libraries (both static (.a) and shared (.so)) to come afterwards. And, if you have multiple libraries, they have to be ordered as well: the "highest level" libraries come first and the "lower-level" libraries (libraries needed by other libraries) come afterward.

As for your makefile, as mentioned in the comments you can't add libraries to your CFLAGS variable; this is wrong:

tests: CFLAGS += $(TARGET)

that's why you have your library on your link line before your object files, because CFLAGS comes before object files in the default recipe you're using.

You should use this instead:

$(TESTS): $(TARGET)

this ensures that your library is built before your tests, and also that they will be included in the link line (after the list_tests.c)

MadScientist
  • 92,819
  • 9
  • 109
  • 136