使用unitests构建我自己的库会抱怨未定义的引用

时间:2016-07-18 16:55:50

标签: c makefile

我正在构建一个带有一些最简单设施的简单库。然而,make抱怨在构建unitest时存在未定义的引用。我已经发布了所有代码和makefile。这是什么原因?

文件层次结构:

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

生成文件:

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

在/ src目录中,我有两个文件ex30.c,libex30.c和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

在/ tests中,我有libex30_tests.c,minunit.h和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 ""

这是我在制作之后得到的:

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

3 个答案:

答案 0 :(得分:2)

这些符号由-ldl导出。与{{1}}链接。

答案 1 :(得分:0)

您应该在Makefile sg中添加一条规则,如下所示:

%: %.c ${OTHER_OBJECTS_AND_ARCHIVES}
        ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} -o $@ $^ ${LIBS}

答案 2 :(得分:0)

您的Makefile中存在多个问题。而且,我不得不猜测一下正确的文件层次结构。而且,您的list.c不完整

Makefile中的一个大问题就是您用来构建测试的这一行:

tests: CFLAGS += $(TARGET)

$(TARGET)是您的图书馆.a文件。通过这种方式指定命令,无论何时构建测试,您都会得到一个命令(例如):

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

问题是{<1}}在编译 lib.a之前会检测依赖关系,因此mytest1.c想要来自mytest1.c的符号是<链接器知道em> not ,因此没有从库中提取任何内容。

正确的命令是:

lib.a

要在cc -o mytest1 -O2 -g mytest1.c lib.a 中正确指定,请参阅以下更改。

我修复了你的Makefile并注释了上述错误以及其他一些错误:

Makefile

提醒:由于代码块在SO上的发布方式,makefile中的标签会转换为空格。因此,当您拉动makefile时,您需要将一行中的前导空格转换为单个选项卡。否则,您会看到(例如)可怕的# 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

这是我最终得到的文件层次结构:

Makefile:59: *** missing separator.  Stop.

makefile可以正常工作。请注意,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 可能是(例如)elixir,因为makefile现在执行/home/elixir/projects/.../mylib

但是,如果[无意中]把东西放在错误的地方,你当然可以移动它们,但你可能需要稍微调整pwd

此外,即使发布了Makefile,它也是不完整的,只定义了list.c中大约一半的函数。这个bug被主要的makefile bug掩盖了。所以,我添加了虚拟版本以获得干净的构建:

list.h

我在修复后得到的输出看起来像这样:

#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;
}

<强>更新

  

我为这个错误道歉。我意识到我混合了这两个项目...在第三次更新后...我已经纠正了错误并且我已经给出了文件层次结构。你能看看更新后的帖子吗?它的文件较少,并没有真正使用链表代码。

注意输出。 成功运行非列表测试。如果您在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 中填写虚拟函数,列表测试也会起作用。

实际上,list.c项目的src子目录不是ex30的子目录,而是使用ex30的子目录会更清晰。

但是,我怀疑你最终想要的是什么:

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

请注意,Makefile的大部分内容将移至rules.mk,每个Makefile只会移动几行。而且,您可以添加更多具有类似子层次结构的项目。

IMO,一个可能更清晰的变种是:

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

可为每个项目添加src子目录

build一起。但是,我建议build是一个顶级目录,它有每个项目的子目录:

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

这样做的好处是源树层次结构.o.a和可执行文件(即)重建的东西混杂在一起。这样可以更轻松地设置git,因为您不需要每个项目.gitignore文件。

YMMV,但是,我已经在每个可以想象的组织中为我自己的东西做了这个,这是我经过多次试验和错误后我已经确定的那个。

如果可以,我可以相应调整并重新发布。

为了给你一个更强大的rules.mk文件的更具体的例子,这里有一个我很多用于SO问题。

注意:这只是一个例子。它可以自己使用,因为它依赖于几个包装脚本来运行。不要试图使用或改编它。最适合您的方法是使用原始rules.mk作为起点,从头开始构建自己的Makefile

# 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

以下是使用它的每个项目Makefile

# 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