0

I am building a simple library with some unitest facilities. However, the make complains that there is undefined reference while building the unitest. I have posted all the code and makefile. What is the reason for this?

File hierarchy:

/bin
/build
  Makefile
/src
  dbg.h
  ex30.c
  libex30.c  
/tests
  dbg.h
  libex30_tests.c
  minuint.h
  runtests.sh

Makefile:

CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)
LIBS=-ldl $(OPTLIBS)
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/libYOUR_LIBRARY.a
#SO_TARGET=$(patsubst %.a,%.so,$(TARGET))

# The Target Build
#all: $(TARGET) $(SO_TARGET) tests
all: $(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

In /src dirctory, I have two files ex30.c, libex30.c, and dbg.h

ex30.c:

#include <stdio.h>
#include "dbg.h"
#include <dlfcn.h>

typedef int (*lib_function)(const char *data);


int main(int argc, char *argv[])
{
    int rc = 0;
  check(argc == 4, "USAGE: ex30 libex30.so function data");

  char *lib_file = argv[1];
  char *func_to_run = argv[2];
  char *data = argv[3];

  void *lib = dlopen(lib_file, RTLD_NOW);
  check(lib != NULL, "Failed to open the library %s: %s", lib_file, dlerror());

  lib_function func = dlsym(lib, func_to_run);
  check(func != NULL, "Did not find %s function in the library %s: %s", func_to_run, lib_file, dlerror());

  rc = func(data);
  check(rc == 0, "Function %s return %d for data: %s", func_to_run, rc, data);

  rc = dlclose(lib);
  check(rc == 0, "Failed to close %s", lib_file);

  return 0;

error:
  return 1;
}

libex30.c:

#include <stdio.h>
#include <ctype.h>
#include "dbg.h"


int print_a_message(const char *msg)
{
    printf("A STRING: %s\n", msg);
    return 0;
}

int uppercase(const char *msg)
{
    int i = 0;
  // BUG: \0 termination problems
  for(i = 0; msg[i] != '\0'; i++) {
        printf("%c", toupper(msg[i]));
  }

  printf("\n");
    return 0;
}

int lowercase(const char *msg)
{
    int i = 0;

  // BUG: \0 termination problems
  for(i = 0; msg[i] != '\0'; i++) {
    printf("%c", tolower(msg[i]));
  }

  printf("\n");
  return 0;
}

int fail_on_purpose(const char *msg)
{
    return 1;
}

dbg.h:

#ifndef __dbg_h__
#define __dbg_h__

#include <stdio.h>
#include <errno.h>
#include <string.h>

#ifdef NDEBUG
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d:%s: " M "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#endif

#define clean_errno() (errno == 0 ? "None" : strerror(errno))

#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define check(A, M, ...) if(!(A)) {log_err(M, ##__VA_ARGS__); errno=0; goto error;}

#define sentinel(M, ...) {log_err(M, ##__VA_ARGS__); errno=0; goto error;}

#define check_mem(A) check((A), "Out of memory.")

#define check_debug(A, M, ...) if(!(A)){debug(M, ##__VA_ARGS__); errno=0; goto error;}

#endif

In /tests, I have libex30_tests.c, minunit.h, and dbg.h:

libex30_tests.c:

#include "minunit.h"

char *test_dlopen()
{

    return NULL;
}

char *test_functions()
{

    return NULL;
}

char *test_failures()
{

    return NULL;
}

char *test_dlclose()
{

    return NULL;
}

char *all_tests() {
    mu_suite_start();

    mu_run_test(test_dlopen);
    mu_run_test(test_functions);
    mu_run_test(test_failures);
    mu_run_test(test_dlclose);

    return NULL;
}

RUN_TESTS(all_tests);

minunit.h

#undef NDEBUG
#ifndef _minunit_h
#define _minunit_h

#include <stdio.h>
#include "dbg.h"
#include <stdlib.h>

#define mu_suite_start() char *message = NULL

#define mu_assert(test, message) if (!(test)) { log_err(message); return message; }
#define mu_run_test(test) debug("\n-----%s", " " #test); \
    message = test(); tests_run++; if (message) return message;

#define RUN_TESTS(name) int main(int argc, char *argv[]) {\
    argc = 1; \
    debug("----- RUNNING: %s", argv[0]);\
    printf("----\nRUNNING: %s\n", argv[0]);\
    char *result = name();\
    if (result != 0) {\
        printf("FAILED: %s\n", result);\
    }\
    else {\
        printf("ALL TESTS PASSED\n");\
    }\
    printf("Tests run: %d\n", tests_run);\
    exit(result != 0);\
}


int tests_run;

#endif

runtests.sh:

echo "Running unit tests:"

for i in tests/*_tests
do
    if test -f $i
    then
        if $VALGRIND ./$i 2>> tests/tests.log
        then
            echo $i PASS
        else
            echo "ERROR in test $i: here's tests/tests.log"
            echo "------"
            tail tests/tests.log
            exit 1
        fi
    fi
done

echo ""

Here is what I get after make:

cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG  build/libYOUR_LIBRARY.a    tests/libex30_tests.c   -o tests/libex30_tests
In file included from tests/libex30_tests.c:1:0:
tests/libex30_tests.c: In function ‘main’:
tests/minunit.h:15:38: warning: parameter ‘argc’ set but not used [-Wunused-but-set-parameter]
 #define RUN_TESTS(name) int main(int argc, char *argv[]) {\
                                      ^
tests/libex30_tests.c:38:1: note: in expansion of macro ‘RUN_TESTS’
 RUN_TESTS(all_tests);
 ^
/tmp/ccqde9jD.o: In function `main':
/home/rex/rex/projects/programming/c/learn_hard_way/ex30/tests/libex30_tests.c:38: multiple definition of `main'
build/libYOUR_LIBRARY.a(ex30.o):/home/rex/rex/projects/programming/c/learn_hard_way/ex30/src/ex30.c:9: first defined here
build/libYOUR_LIBRARY.a(ex30.o): In function `main':
ex30.c:(.text.startup+0x85): undefined reference to `dlopen'
ex30.c:(.text.startup+0x9c): undefined reference to `dlsym'
ex30.c:(.text.startup+0x120): undefined reference to `dlclose'
ex30.c:(.text.startup+0x188): undefined reference to `dlerror'
ex30.c:(.text.startup+0x1f0): undefined reference to `dlerror'
collect2: error: ld returned 1 exit status
make: *** [tests/libex30_tests] Error 1
drdot
  • 3,215
  • 9
  • 46
  • 81
  • You are not linking against the lib containing `dlopen` et. al. – Mad Physicist Jul 18 '16 at 16:59
  • @MadPhysicist, could you elaborate on what to change in the Makefile? – drdot Jul 18 '16 at 17:13
  • Are you sure the posted files are correct? There's no `runtests.sh` and no `lcthw/list.c` (which isn't needed to build). I downloaded everything. I built it. I ran `libex30_tests` directly and it printed output for test_dlopen, test_functions, test_failures, test_dlclose and then said `ALL TESTS PASSED` and `Tests run: 4`. So, the build isn't completely clean, but it does seem to work [without knowing what the `*.sh` script does]. I also get an empty `build/libYOUR_LIBRARY.a`. More info? BTW, there's probably a simpler way than the `RUN_TESTS` CPP macro – Craig Estey Jul 20 '16 at 04:48
  • @CraigEstey, sorry about that. I updated. Now you should have everything. – drdot Jul 20 '16 at 05:06
  • Sorry to be a nitpicker :-), but still missing `lcthw/list.h` ... – Craig Estey Jul 20 '16 at 05:28
  • @CraigEstey, ah, sorry. you got it. – drdot Jul 20 '16 at 05:30
  • Same caveat :-), but missing whatever `.c` defines `List_create` et. al. (i.e. I'm getting an undefined reference during link) – Craig Estey Jul 20 '16 at 05:52
  • @CraigEstey, that is defined in list.h and list.c. Both are placed in src/lcthw/. Could you elaborate your question? I dont think I understand what you asked. If you are getting the same error, that is good. Can you find a way to fix that? – drdot Jul 20 '16 at 05:55
  • Easy. I'm missing `list.c` which is not posted [AFAICT--per my first comment]. But, no rush. It's late here, so I'll have to continue this tomorrow. – Craig Estey Jul 20 '16 at 06:31
  • @CraigEstey, you got it – drdot Jul 20 '16 at 06:53
  • Possible duplicate of [In C programming, what is \`undefined reference\`error, when compiling?](http://stackoverflow.com/questions/29998485/in-c-programming-what-is-undefined-referenceerror-when-compiling) – too honest for this site Jul 22 '16 at 20:37

3 Answers3

2

Those symbols are exported by libdl.so. Link with -ldl.

Lawrence D'Anna
  • 2,998
  • 2
  • 22
  • 25
0

You should add a rule into your Makefile sg like this:

%: %.c ${OTHER_OBJECTS_AND_ARCHIVES}
        ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} -o $@ $^ ${LIBS}
Zsigmond Lőrinczy
  • 377
  • 1
  • 4
  • 9
  • Does not work. Given errors like this: tests/list_tests.c:13: undefined reference to `List_create' /tmp/ccREMNYA.o: In function `test_destroy': – drdot Jul 19 '16 at 04:41
  • Of course you have to add the other sources/object-modules/ar-libraries instead of the placeholder `${OTHER_OBJECTS_AND_ARCHIVES}` – Lorinczy Zsigmond Jul 19 '16 at 09:06
0

There were several issues in your Makefile. And, I had to guess a bit about the correct file hierarchy. And, your list.c was incomplete


The big bug in your Makefile was this line which you used to build your tests:

tests: CFLAGS += $(TARGET)

$(TARGET) is your library .a file. By specifying the command this way, whenever you were building a test you got a command (e.g.):

cc -o mytest1 -O2 -g lib.a mytest1.c

The problem is that lib.a gets scanned for dependencies before mytest1.c is compiled, so the symbols that mytest1.c wants from lib.a are not known by the linker, so nothing is pulled from the library.

The correct command is:

cc -o mytest1 -O2 -g mytest1.c lib.a

To specify this correctly in the Makefile, see the changes below.


I've fixed your Makefile and annotated the above bug, along with a few others:

# NOTE/BUG: there are issues with _not_ using full path. they _can_ be solved
# without doing this, but this makes things easier
SRC := $(shell pwd)

# NOTE: to just _build_ the tests but _not_ try to run them, specify:
#   RUNTESTS=tests
# on the command line
RUNTESTS ?= runtests

# NOTE: cosmetic change to library name
###LIBNAME = YOUR_LIBRARY
LIBNAME = lcthw

CFLAGS += -g
CFLAGS += -O2
CFLAGS += -Wall

# NOTE: IMO, -Wextra is overkill and causes more problems than it's worth
# what I do is something like this and then do:
#   make CMDLINE_CFLAGS=-Wextra
# on those rare occasions where it might be useful
###CFLAGS += -Wextra
CFLAGS += $(CMDLINE_CFLAGS)

CFLAGS += -I$(SRC) -rdynamic -DNDEBUG $(OPTFLAGS)

LIBS=-ldl $(OPTLIBS)
PREFIX?=/usr/local

# library sources and objects
# NOTE/BUG: the wildcard was failing
###SOURCES=$(wildcard src/**/*.c src/*.c)
SOURCES=$(wildcard lcthw/*.c)
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))

# test sources and objects
TEST_SRC=$(wildcard tests/*_tests.c)
TEST_OBJS=$(patsubst %.c,%.o,$(TEST_SRC))
TESTS=$(patsubst %.o,%,$(TEST_OBJS))

# library target
TARGET=build/lib$(LIBNAME).a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))
TARGETS += $(TARGET)
# NOTE: comment the following out to _not_ build the shared library -- it's
# _not_ used but will be built now (to use it, change TARGET to SO_TARGET when
# building TESTS)
TARGETS += $(SO_TARGET)

# The Target Build
all: $(TARGETS) $(RUNTESTS)

###dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: CFLAGS=-g -Wall -I$(SRC) -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
# NOTE/BUG: in order to link properly $(TARGET) must be the last part of the
# command
#
# (i.e.) doing CFLAGS += $(TARGET) produced the equivalent of:
#   $(CC) -o $@ $(CFLAGS) $(TARGET) $@.c
#
# instead of:
#   $(CC) -o $@ $(CFLAGS) $@.c $(TARGET)
#
# the reason the first method failed was because the library was scanned
# _before_ the .c was compiled, so it didn't know to pull any .o files from
# it
#
# the second example _will_ work and is closer to what you originally specified,
# but my personal preference is to always build the .o files:
$(TESTS):
    $(CC) -c $(CFLAGS) -o $@.o $@.c
    $(CC) -o $@ $(CFLAGS) $@.o $(TARGET)

# just build the tests
.PHONY: tests
tests: $(TESTS)

# build and run the tests
.PHONY: runtests
runtests: $(TESTS)
    sh ./tests/runtests.sh

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

# The Cleaner
clean:
    rm -f *.o
    rm -rf bin build $(OBJECTS) $(TESTS)
    rm -f $(TEST_OBJS)
    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

Reminder: Due to the way code blocks are posted on SO, the tabs in the makefile get converted to spaces. So, when you pull the makefile, you'll need to convert leading whitespace on a line to a single tab. Otherwise, you'll see (e.g.) the dreaded Makefile:59: *** missing separator. Stop.


Here's the file hierarchy I ended up with:

elixir/ex30.c
elixir/lcthw/list.c
elixir/lcthw/list.h
elixir/libex30.c
elixir/Makefile
elixir/tests/dbg.h
elixir/tests/libex30_tests.c
elixir/tests/list_tests.c
elixir/tests/minunit.h
elixir/tests/runtests.sh

The makefile works correctly against that. Note that elixir could be (e.g.) /home/elixir/projects/.../mylib as the makefile now does a pwd

But, if [inadvertently] put things in the wrong place, you can certainly move them, but you may have to adjust the Makefile a bit.


Also, even though list.c was posted, it was incomplete and defined only about half of the functions in list.h. This bug was masked by the primary makefile bug. So, I added dummy versions to get a clean build:

#include <lcthw/list.h>
//#include <lcthw/dbg.h>

List *List_create()
{
    return calloc(1, sizeof(List));
}

void List_destroy(List *list)
{
    LIST_FOREACH(list, first, next, cur) {
        if(cur->prev) {
            free(cur->prev);
        }
    }

    free(list->last);
    free(list);
}

void List_clear(List *list)
{
    LIST_FOREACH(list, first, next, cur) {
        free(cur->value);
    }
}

void List_clear_destroy(List *list)
{
    List_clear(list);
    List_destroy(list);
}

void
List_push(List *list, void *value)
{
}

void *
List_pop(List *list)
{
    return NULL;
}

void
List_unshift(List *list, void *value)
{
}

void *
List_shift(List *list)
{
    return NULL;
}

void *
List_remove(List *list, ListNode *node)
{
    return NULL;
}

The output I'm getting after the fixes looks something like this:

cc -g -O2 -Wall  -I/home/myhome/elixir -rdynamic -DNDEBUG  -fPIC   -c -o lcthw/list.o lcthw/list.c
ar rcs build/liblcthw.a lcthw/list.o
ranlib build/liblcthw.a
cc -shared -o build/liblcthw.so lcthw/list.o
cc -c -g -O2 -Wall  -I/home/myhome/elixir -rdynamic -DNDEBUG  -o tests/list_tests.o tests/list_tests.c
cc -o tests/list_tests -g -O2 -Wall  -I/home/myhome/elixir -rdynamic -DNDEBUG  tests/list_tests.o build/liblcthw.a
cc -c -g -O2 -Wall  -I/home/myhome/elixir -rdynamic -DNDEBUG  -o tests/libex30_tests.o tests/libex30_tests.c
cc -o tests/libex30_tests -g -O2 -Wall  -I/home/myhome/elixir -rdynamic -DNDEBUG  tests/libex30_tests.o build/liblcthw.a
sh ./tests/runtests.sh
Running unit tests:
----
RUNNING: ./tests/libex30_tests
ALL TESTS PASSED
Tests run: 4
tests/libex30_tests PASS
----
RUNNING: ./tests/list_tests
FAILED: Wrong last value.
Tests run: 2
ERROR in test tests/list_tests: here's tests/tests.log
------
DEBUG tests/libex30_tests.c:32:all_tests:
----- test_failures
DEBUG tests/libex30_tests.c:33:all_tests:
----- test_dlclose
DEBUG tests/list_tests.c:113:main: ----- RUNNING: ./tests/list_tests
DEBUG tests/list_tests.c:103:all_tests:
----- test_create
DEBUG tests/list_tests.c:104:all_tests:
----- test_push_pop
[ERROR] (tests/list_tests.c:32: errno: None) Wrong last value.
Makefile:96: recipe for target 'runtests' failed
make: *** [runtests] Error 1

UPDATE:

I apologize for the mistake. I realize I have mixed the two projects...after updating the third time...I have corrected the mistake and i have given the file hierarchy. Could you take a look at the updated post? It has less files and does not really use the linked list code.

Notice the output. It did run the non-list tests successfully. If you fill in the dummy functions in list.c, the list tests would also work.

As it is, instead of a subdir of src for the ex30 project, it would be cleaner to use a subdir of ex30.

But, what I suspect what you really want [ultimately] is something like:

elixir/common/dbg.h
elixir/common/rules.mk

elixir/ex30/ex30.c
elixir/ex30/libex30.c
elixir/ex30/Makefile

elixir/lcthw/list.c
elixir/lcthw/list.h
elixir/lcthw/Makefile

elixir/tests/libex30_tests.c
elixir/tests/list_tests.c
elixir/tests/Makefile
elixir/tests/minunit.h
elixir/tests/runtests.sh

Note that the bulk of Makefile would be moved to rules.mk and each Makefile would be just a few lines. And, you can add more projects with a similar sub-hierarchy.

IMO, a possibly cleaner variant is:

elixir/common/dbg.h
elixir/common/rules.mk

elixir/ex30/ex30.c
elixir/ex30/libex30.c
elixir/ex30/libex30_tests.c
elixir/ex30/Makefile

elixir/lcthw/list.c
elixir/lcthw/list.h
elixir/lcthw/list_tests.c
elixir/lcthw/Makefile

elixir/tests/Makefile
elixir/tests/minunit.h
elixir/tests/runtests.sh

The src subdir could be added for each project.

Along with build. But, what I'd recommend for build is a top level directory that has per-project subdirs:

elixir/build/ex30/libex30_tests
elixir/build/ex30/libex30_tests.o

elixir/build/lcthw/lcthw.a
elixir/build/lcthw/list.o
elixir/build/lcthw/list_tests.o
elixir/build/lcthw/list_tests

This has the advantage that the source tree hierarchy is not cluttered with .o, .a and executables (i.e.) things that get rebuilt. This makes setup for git easier because you don't need a per-project .gitignore file.

YMMV, but, I've done this for my own stuff in every conceivable organization, and this is the one I've settled on, after much trial and error.

If that is okay with you I can adjust accordingly and repost.


To give you a more concrete example of a more powerful rules.mk file, here is one I use a lot for SO questions.

Note: This is just an example. It can not be used by itself because it relies upon several wrapper scripts to operate. Don't try to use or adapt it. The best route for you is to build up your own rules.mk from scratch, using your original Makefile as a starting point.

# rules/rules.mk -- ovrstk rules control
#
# options:
#   GDB -- enable debug symbols
#     0 -- normal
#     1 -- use -O0 and define _USE_GDB_=1
#
#   CLANG -- use clang instead of gcc
#     0 -- use gcc
#     1 -- use clang
#
#   CVERBOSE -- build verbosity
#     0 -- normal
#     1 -- add -v to cc and -Wl,--verbose to ld
#
#   MKVERBOSE -- makefile/rules verbosity
#     0 -- normal
#     1 -- verbose
#
#   M32 -- cross-build to 32 bit mode
#     0 -- native build
#     1 -- build for i386
#
#   BNC -- enable benchmarks
#     0 -- normal mode
#     1 -- enable benchmarks for function enter/exit pairs
#     2 -- add min/max
#
#   XCFLAGS -- extra command line CFLAGS
#   XDFLAGS -- extra command line DFLAGS
#
#   DOT_I -- generate preprocessor output
#   DOT_S -- generate assembler output
#
#   GLIB -- added options for glib
#
# symbols:
#   DFLAGS -- -D options
#   CFLAGS -- compiler options
#
#   PREP -- targets to execute before ALL
#   ALL -- things to build (automatically uses LIBNAME/PGMTGT)
#
#     LIBNAME -- library name to build (e.g. foo.a) -- can be multiple
#     OLIB -- list of .o files to build LIBNAME
#     OLIB-<libname> -- list of .o files to build <libname>
#
#     PGMTGT -- program to build (e.g. fludger) -- can be multiple
#     OLIST -- list of .o files to build PGMTGT
#     OLIST-<pgmname> -- list of .o files to build <pgmname>
#
#   CLEAN -- things to clean (automatically uses a bunch of stuff)
#
#   NOPROTO -- do not generate prototypes
#   FINLINE -- allow gcc to inline functions automatically
#   WNOERROR -- inhibit -Werror
#   WEXTRA -- add -Wextra

ifdef OVRPUB
  ifndef SDIR
    SDIR := $(shell pwd)
    STAIL := $(notdir $(SDIR))
  endif

  ifndef GENTOP
    GENTOP := $(dir $(SDIR))
  endif

  ifndef GENDIR
    GENDIR := $(GENTOP)/$(STAIL)
  endif

  ifndef ODIR
    ODIR := $(GENDIR)
  endif

  NOPROTO := 1
endif

# disable prototype generation
ifdef NOPROTO
  PROTOLST := true
  PROTOGEN := @true
else
  PROTOLST := qproto
  PROTOGEN := @qproto
  PROTOALL := proto
endif

ifndef SDIR
  $(error rules: SDIR not defined)
endif
ifndef ODIR
  $(error rules: ODIR not defined)
endif
ifndef GENDIR
  $(error rules: GENDIR not defined)
endif
ifndef GENTOP
  $(error rules: GENTOP not defined)
endif

ifndef _rules_mk_
_rules_mk_ = 1

  CLEAN += $(LIBNAME) $(PGMTGT)

  ifndef NOPROTO
    CLEAN += *.proto
  endif

  CLEAN += *.a
  CLEAN += *.o
  CLEAN += *.i
  CLEAN += *.dis
  CLEAN += *.lst
  CLEAN += *.TMP

  QPROTO := $(shell $(PROTOLST) -i -l -O$(GENTOP) $(SDIR)/*.c $(CPROTO))
  HDEP += $(QPROTO)

  ###VPATH += $(GENDIR)
  ###VPATH += $(SDIR)

  ifdef INCLUDE_MK
    -include $(INCLUDE_MK)
  endif

  ifdef M32
    CFLAGS += -m32
  endif

  ifdef CVERBOSE
    CFLAGS += -v
  endif

  ifdef MKVERBOSE
    MSG := @echo
  else
    MSG := @true
  endif

  ifdef GSYM
    CFLAGS += -gdwarf-2
  endif

  ifdef GDB
    CFLAGS += -gdwarf-2
    DFLAGS += -D_USE_GDB_
  else
    CFLAGS += -O2
  endif

  ifdef DEBUG
    DFLAGS += -DDEBUG=$(DEBUG)
  endif

  ifndef ZPRT
    DFLAGS += -D_USE_ZPRT_=0
  endif

  ifdef BNC
    DFLAGS += -D_USE_BNC_=$(BNC)
  endif

  ifdef GLIB
    _GLIB = glib-2.0
    DFLAGS += $(shell pkg-config --cflags $(_GLIB))
    STDLIB += $(shell pkg-config --libs $(_GLIB))
  endif

  ifdef CLANG
    CC := clang
    CXX := clang++
  else
    ifdef CPLUS
      ifeq ($(STDLIB),)
        STDLIB += -lstdc++
      endif
    endif
  endif

  # alternate -std
  # NOTE: clang++ does not understand c++17
  ifdef CSTD
    _CSTD := $(CSTD)
    ifdef CLANG
      ifdef CPLUS
        ifeq ($(CSTD),c++17)
          _CSTD := c++1y
        endif
      endif
    endif
    CFLAGS += -std=$(_CSTD)
  endif

  ifdef MPI
    export PATH := /usr/lib64/openmpi/bin:$(PATH)
    CC := mpicc
    CXX := mpicxx
  endif

  DFLAGS += -I$(GENTOP)
  DFLAGS += -I$(OVRTOP)
  DFLAGS += -I$(OVRBNC)

  CFLAGS += -Wall
  ifndef WNOERROR
    CFLAGS += -Werror
  endif
  ifdef WEXTRA
    CFLAGS += -Wextra
  endif
  CFLAGS += -Wno-unknown-pragmas
  CFLAGS += -Wempty-body
  CFLAGS += -fno-diagnostics-color

  ifdef DOT_I
    NOLDC := @true
    COPTS += -E -P
    O := i
  endif

  ifdef DOT_S
    NOLDC := @true
    COPTS += -S
    SOPTS += -E -P
    O := s
  endif

  ifndef COPTS
    COPTS += -c
    O := o
  endif

  ifeq ($(O),o)
    ALL += $(LIBNAME) $(PGMTGT)
  else
    ALL += $(addsuffix .$(O),$(basename $(PGMTGT) $(OLIST)))
  endif

  ifndef FINLINE
    # NOTE: we now need this to prevent inlining (enabled at -O2)
    ifndef CLANG
      CFLAGS += -fno-inline-small-functions
    endif

    # NOTE: we now need this to prevent inlining (enabled at -O3)
    CFLAGS += -fno-inline-functions
  endif

  ifdef FIXREG
    ifndef CLANG
      CFLAGS += $(FIXREG)
    endif
  endif

  ifndef F95
    F95 := f95
  endif

  ifndef LDC
    ifdef FTN
      LDC = $(F95)
    endif
  endif

  ifndef LDC
    ifdef CPLUS
      LDC = $(CXX)
    else
      LDC = $(CC)
    endif
  endif

  # FIXME/CAE -- gold wiki page says use -Wl but it seems to have no effect
  ifndef CLANG
    ###LDC += -Wl,-fuse-ld=ld.blah
  endif

  ifdef CVERBOSE
    LDC += -Wl,-verbose=2
  endif

  ifdef LIBLIST
    LIBLIST := $(addprefix $(GENTOP)/,$(LIBLIST))
  endif

  CFLAGS += $(XCFLAGS)
  DFLAGS += $(XDFLAGS)

  CFLAGS += $(DFLAGS)
endif

all: $(PREP) $(PROTOALL) $(ALL)

# C
%.o: %.c $(HDEP)
    $(MSG) %.o %.c
    $(CC) $(CFLAGS) $(COPTS) -o $*.$(O) $<

%.i: %.c
    $(MSG) %.i %.c
    cpp $(DFLAGS) -P $< > $*.i

%.s: %.c
    $(MSG) %.s %.c
    $(CC) $(CFLAGS) -S -o $*.s $<

# C++
%.o: %.cpp $(HDEP)
    $(MSG) %.o %.cpp
    $(CXX) $(CFLAGS) $(COPTS) -o $*.$(O) $<

%.i: %.cpp
    $(MSG) %.i %.cpp
    cpp $(DFLAGS) -P $< > $*.i

%.s: %.cpp
    $(MSG) %.s %.cpp
    $(CXX) $(CFLAGS) -S -o $*.s $<

# asm
%.o: %.s $(HDEP)
    $(MSG) %.o %.s
    $(AS) $(AFLAGS) -o $*.$(O) $<

%.o: %.S $(HDEP)
    $(MSG) %.o %.S
    $(CC) $(CFLAGS) $(COPTS) $(SOPTS) -o $*.$(O) $<

# fortran
%.o: %.f95 $(HDEP)
    $(MSG) %.o %.f95
    $(F95) -c -o $*.$(O) $<

.SECONDEXPANSION:
###.SUFFIXES:

# build a library (type (2) build)
$(LIBNAME):: $(OLIB) $$(OLIB-$$@)
    $(NOLDC) ar rv $@ $^

# build programs
$(PGMTGT):: $$@.o $(OLIST) $$(OLIST-$$@) $(LIBLIST)
    $(NOLDC) $(LDC) $(CFLAGS) -o $@ $^ $(STDLIB)

.PHONY: proto
proto::
    $(PROTOGEN) -i -v -O$(GENTOP) $(SDIR)/*.c $(CPROTO)

.PHONY: clean
clean::
    rm -f $(CLEAN)

.PHONY: help
help::
    egrep '^#' $(SDIR)/Makefile

Here is a per-project Makefile that uses it:

# fastread/Makefile -- make file for fastread
#
# SO: read line by line in the most efficient way platform specific
# SO: questions/33616284

ifndef _fastread_mk_
_fastread_mk_ = 1

  LIBNAME = fastread
  OLIB += rdgets.o
  OLIB += rdmmap.o
  OLIB += lib.o
  OLIB += node1.o
  OLIB += node2.o

  PGMTGT = xrdfile
  OLIST += rdgets.o
  OLIST += rdmmap.o
  OLIST += lib.o
  OLIST += node1.o
  OLIST += node2.o
  ###LIBLIST = fastread/fastread.a

  HDEP += fastread.h

  DFLAGS += -I$(SDIR)
endif

include $(OVRTOP)/rules/rules.mk
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • I apologize for the mistake. I realize I have mixed the two projects...after updating the third time...I have corrected the mistake and i have given the file hierarchy. Could you take a look at the updated post? It has less files and does not really use the linked list code. – drdot Jul 22 '16 at 03:32
  • Many many thank you. I have learned a great deal from your post. "If that is okay with you I can adjust accordingly and repost." if I understand you correctly, you meant repost the one with the .o and .a generated in the build dir? Yes, that sounds perfect! – drdot Jul 24 '16 at 03:43
  • If you think my modified hierarchy is okay, I can adjust the last version of your `Makefile`, as I posted, splitting off the `rules.mk`. I still have copies of all your files. So, what I would need would be the hierarchy that _you'd_ like to use for the near future for your [multiple] projects. That is, I _don't_ need the `.c` and `.h` files reposted. Also, possibly with some of your reasoning as to why you want the hierarchy one way vs another. I'll comment back if I see any red flags. Or, just trust my judgement ;-) – Craig Estey Jul 24 '16 at 04:36
  • It's late here, so I'll do it tomorrow. – Craig Estey Jul 24 '16 at 05:29
  • Is this the final version? – drdot Jul 27 '16 at 16:45
  • Sorry for the delay. The current version _is_ usable. I've been busy elsewhere. But, when adapting the rules.mk file, I've been trying to come up with a version that does _not_ use `.SECONDEXPANSION:` as that has been problematic for me [i.e. it works _if_ there are wrapper scripts, but would have difficulty without them]. Also, I'm removing some of the options that _only_ pertain to my projects to reduce clutter. So, I haven't forgotten about this, it's just taking a little longer than I had hoped for. – Craig Estey Jul 27 '16 at 17:35