Introducing GNU Build System (aka Autotools)

by $4

CC BY-SA 3.0 TW

https://fourdollars.github.io/autotools/

git clone https://github.com/fourdollars/autotools.git

第一隻 Hello World 的 C 程式

#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    return 0;
}

使用 GCC (GNU Compiler Collection) 來編譯

$ gcc hello.c -o hello

使用 GNU Make 來協助編譯

hello: hello.o
    gcc -o hello hello.o
hello.o: hello.c
    gcc -c hello.c
$ make

Makefile 的規則介紹

target: dependencies ... ; command
    commands
    ...
hello: hello.o
    gcc -o hello hello.o

hello.o: hello.c
    gcc -c hello.c
假目標 .PHONY 以及 潛規則 Implicit Rules
hello: hello.o

clean: ; $(RM) hello hello.o
    @echo done

.PHONY: clean
%.o : %.c
    $(CC) -Wall -c $(CFLAGS) $(CPPFLAGS) -o $@ $< 

%.o : CFLAGS = -g

Makefile 的變數介紹

objects = program.o foo.o utils.o
program : $(objects)
    cc -o program $(objects)

$(objects) : defs.h
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;@echo $(foo)
x := foo
y := $(x) bar
x := later
xy:;@echo $(x) $(y)
a := foo
a ?= bar
b := $(a)
ab:;@echo $(a) $(b)
objects = main.o foo.o bar.o utils.o
objects += another.o

Makefile 的函式介紹

files := $(wildcard *.c *.html)
files:;@echo $(files)
subst := $(subst html,HTML,$(files))
subst:;@echo $(subst)
patsubst := $(patsubst %.c,%.o,$(files))
patsubst:;@echo $(patsubst)
list := 'a   b   c   '
list:;@echo $(list)
strip := $(strip $(list))
strip:;@echo $(strip)
suffix := $(suffix $(files))
suffix:;@echo $(suffix)
$(error   This is error.)
$(warning This is warning.)
$(info    This is info.)

Makefile 的條件判斷

ifeq ($(CC),gcc)
    CPPFLAGS = -Wall
    CFLAGS = -g
endif
ifdef DEBUG
    CPPFLAGS = -Wall
    CFLAGS = -g
endif
list_files = $(foreach item,$(1),$(info $(item)))
info_msg = $(info $(1))

$(if $(findstring make.html, $(files)), \
    $(call list_files, $(files)), \
    $(call info_msg, 'I can not find make.html') \
)

線上文件
GNU Make Manual

簡單的 Library 函式庫

/* hello_world.h */
#ifndef __HELLO_WORLD__
#define __HELLO_WORLD__
#ifdef __cplusplus
extern "C" /* Or using G_BEGIN_DECLS from <glib.h> */
{
#endif
    /** 
     * @brief This is a hello world library.
     * 
     * @return The reture value of printf.
     */
    int hello_world(void);
#ifdef __cplusplus
} /* Or using G_END_DECLS from <glib.h> */
#endif
#endif
/* hello_world.c */
#include <stdio.h>
#include "hello_world.h"

int hello_world(void)
{
    return printf("Hello World!\n");
}

使用 GCC (GNU Compiler Collection) 來編譯

靜態函式庫 Static Library

$ gcc -c hello_world.c
$ ar cr libhello_world.a hello_world.o

動態函式庫 Shared Library

$ gcc -c -fPIC hello_world.c
$ gcc -shared -fPIC -o libhello_world.so hello_world.o
/* main.c */
#include <hello_world.h>
int main(int argc, char* argv[]) { hello_world(); return 0; }
$ gcc -I. -c main.c
$ gcc -static -o main main.o -L. -lhello_world
$ ./main
$ gcc -I. -c main.c
$ gcc -o main main.o -L. -lhello_world
$ LD_LIBRARY_PATH=. ./main

使用 GNU Make 來協助編譯以及安裝

SOURCES = hello_world.c
HEADERS = hello_world.h
CPPFLAGS += -fPIC
PREFIX ?= /usr/local

LIB = $(addprefix lib,$(patsubst %.c,%,$(firstword $(SOURCES))))
SHARED = $(addsuffix .so,$(LIB))
STATIC = $(addsuffix .a,$(LIB))
OBJS = $(patsubst %.c,%.o, $(SOURCES))

all: $(SHARED) $(STATIC)

$(SHARED): $(OBJS)
    $(CC) -shared -fPIC -o $@ $^

$(STATIC): $(OBJS)
    $(AR) cr $@ $^

$(SOURCES): $(HEADERS)

clean:
    $(RM) $(SHARED) $(STATIC) $(OBJS)

使用 GNU Make 來協助編譯以及安裝 (續)

install: $(SHARED) $(STATIC) $(HEADERS)
    mkdir -p $(DESTDIR)$(PREFIX)/lib
    install $(SHARED) $(DESTDIR)$(PREFIX)/lib
    install $(STATIC) $(DESTDIR)$(PREFIX)/lib
    mkdir -p $(DESTDIR)$(PREFIX)/include
    install $(HEADERS) $(DESTDIR)$(PREFIX)/include

uninstall:
    $(RM) $(DESTDIR)$(PREFIX)/lib/$(SHARED)
    $(RM) $(DESTDIR)$(PREFIX)/lib/$(STATIC)
    $(RM) $(foreach header,$(HEADERS), \
        $(addprefix $(DESTDIR)$(PREFIX)/include/, $(header)))
    -rmdir $(DESTDIR)$(PREFIX)/lib/
    -rmdir $(DESTDIR)$(PREFIX)/include/
    -rmdir $(DESTDIR)$(PREFIX)

.PHONY: clean uninstall

使用 Autotools 來協助編譯 Hello World

準備好 Makefile.am

bin_PROGRAMS = hello
hello_SOURCES = hello.c

執行 autoscan 產生 configure.scan 再修改成 configure.ac

AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
         ↓                    ↓          ↓
AC_INIT([hello],             [1.0],     [bugs@foo.bar])
...
AM_INIT_AUTOMAKE([foreign])

# Checks for programs.

執行 autoreconf -if 產生 configure

$ ./configure
$ make
$ make install

使用 Autotools 來協助編譯 Library 函式庫

準備好 Makefile.am

lib_LTLIBRARIES = libhello_world.la
libhello_world_la_SOURCES = hello_world.c
include_HEADERS = hello_world.h

執行 autoscan 產生 configure.scan 再修改成 configure.ac

AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
         ↓                    ↓          ↓
AC_INIT([libhello_world],    [1.0],     [bugs@foo.bar])
...
AM_INIT_AUTOMAKE([foreign])
LT_INIT

# Checks for programs.

執行 autoreconf -if 產生 configure

$ ./configure
$ make
$ make install

使用 Autotools 的好處之一

利用 DESTDIR 安裝在指定目錄底下

$ export DESTDIR=$PWD
$ make install DESTDIR=$PWD
$ find ./usr/
./usr/
./usr/local
./usr/local/lib
./usr/local/lib/libhello_world.a
./usr/local/lib/libhello_world.so.0.0.0
./usr/local/lib/libhello_world.la
./usr/local/lib/libhello_world.so
./usr/local/lib/libhello_world.so.0
./usr/local/include
./usr/local/include/hello_world.h
$ make uninstall DESTDIR=$PWD
...

使用 Autotools 的好處之二

原始碼的編譯可以跟原始碼分開在不同的目錄

$ make distclean # 要先清理一下原始碼目錄
$ mkdir build && cd build
$ ../configure && make
$ find
.
./libtool
./libhello_world.la
./config.log
./config.h
./.deps
./.deps/hello_world.Plo
./.libs
./.libs/libhello_world.a
./.libs/libhello_world.so.0.0.0
./.libs/libhello_world.la
./.libs/libhello_world.so
./.libs/libhello_world.so.0
./.libs/hello_world.o
./config.status
./hello_world.o
./hello_world.lo
./stamp-h1
./Makefile

使用 Autotools 的好處之三

不同類型的檔案可以安裝到各別指定的目錄底下

$ ../configure --prefix=/opt --libdir=/opt/superlib --includedir=/opt/exclude
$ make && make install DESTDIR=$PWD
$ find ./opt/
./opt/
./opt/exclude
./opt/exclude/hello_world.h
./opt/superlib
./opt/superlib/libhello_world.a
./opt/superlib/libhello_world.so.0.0.0
./opt/superlib/libhello_world.la
./opt/superlib/libhello_world.so
./opt/superlib/libhello_world.so.0
$ ../configure --help
...
Installation directories:
  --prefix=PREFIX         install architecture-independent files in PREFIX
                          [/usr/local]
  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
                          [PREFIX]

By default, `make install' will install all the files in
`/usr/local/bin', `/usr/local/lib' etc.  You can specify
an installation prefix other than `/usr/local' using `--prefix',
for instance `--prefix=$HOME'.
...

libtoolVersioning 系統

$ info libtool
...
7.2 Libtool's versioning system
===============================
...
   Libtool's description of the interfaces that a program uses is
simple: it encodes the least and the greatest interface numbers in the
resulting binary (FIRST-INTERFACE, LAST-INTERFACE).
...
   So, libtool library versions are described by three integers:

CURRENT
     The most recent interface number that this library implements.

REVISION
     The implementation number of the CURRENT interface.

AGE
     The difference between the newest and oldest interfaces that this
     library implements.  In other words, the library implements all the
     interface numbers in the range from number 'CURRENT - AGE' to
     'CURRENT'.
...

libtoolVersioning 系統 (續)

7.3 Updating library version information
========================================
...
  1. Start with version information of '0:0:0' for each libtool library.

  2. Update the version information only immediately before a public
     release of your software.  More frequent updates are unnecessary,
     and only guarantee that the current interface number gets larger
     faster.

  3. If the library source code has changed at all since the last
     update, then increment REVISION ('C:R:A' becomes 'C:r+1:A').

  4. If any interfaces have been added, removed, or changed since the
     last update, increment CURRENT, and set REVISION to 0.

  5. If any interfaces have been added since the last public release,
     then increment AGE.

  6. If any interfaces have been removed or changed since the last
     public release, then set AGE to 0.
...

libtoolVersioning 系統 (續)

configure.ac

...
# Before making a release, the LT_VERSION string should be modified. The
# string is of the form c:r:a. Follow these instructions sequentially:
#   1. If the library source code has changed at all since the last update, then
#      increment revision (‘c:r:a’ becomes ‘c:r+1:a’).
#   2. If any interfaces have been added, removed, or changed since the last
#      update, increment current, and set revision to 0.
#   3. If any interfaces have been added since the last public release, then
#      increment age.
#   4. If any interfaces have been removed or changed since the last public
#      release, then set age to 0.
AC_SUBST([LT_VERSION],[0:0:0])
...

Makefile.am

...
libhello_world_la_LDFLAGS = -version-info $(LT_VERSION)
...

libtoolVersioning 系統 (續)

如果沒有動到API,那麼應該改成 -version-info 0:1:0 的結果。

$ find ./usr
./usr
./usr/local
./usr/local/lib
./usr/local/lib/libhello_world.a
./usr/local/lib/libhello_world.la
./usr/local/lib/libhello_world.so
./usr/local/lib/libhello_world.so.0
./usr/local/lib/libhello_world.so.0.0.1
./usr/local/include
./usr/local/include/hello_world.h

libtoolVersioning 系統 (續)

如果只是新增API,那麼應該改成 -version-info 1:0:1 的結果。

$ find ./usr/
./usr/
./usr/local
./usr/local/lib
./usr/local/lib/libhello_world.a
./usr/local/lib/libhello_world.la
./usr/local/lib/libhello_world.so
./usr/local/lib/libhello_world.so.0
./usr/local/lib/libhello_world.so.0.1.0
./usr/local/include
./usr/local/include/hello_world.h

libtoolVersioning 系統 (續)

如果刪除或修改了API,那麼應該改成 -version-info 1:0:0 的結果。

$ find ./usr/
./usr/
./usr/local
./usr/local/lib
./usr/local/lib/libhello_world.a
./usr/local/lib/libhello_world.la
./usr/local/lib/libhello_world.so
./usr/local/lib/libhello_world.so.1
./usr/local/lib/libhello_world.so.1.0.0
./usr/local/include
./usr/local/include/hello_world.h

參考資料

線上文件
GNU Make Manual
GNOME Developer Center - Programming Guidelines › Maintainer Guidelines » Versioning
info
$ info Autoconf
$ info Automake
$ info Libtool
電子書
Advanced Linux Programming