Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 39.1230 phút đọc

Project: đóng gói mathlib

Sau bài này bạn sẽ làm được

  • Viết Makefile tạo cả hai dạng thư viện từ một bộ nguồn
  • Cài đặt thư viện vào hệ thống đúng chuẩn
  • Viết tệp pkg-config cho thư viện của bạn
  • Chạy qua danh sách nghiệm thu của cả phần

Một bộ mã nguồn, xuất ra cả thư viện tĩnh lẫn thư viện động, cài đặt được vào hệ thống, và người dùng chỉ cần gõ pkg-config --cflags --libs mathlib là dịch được. Đây là bài gói lại toàn bộ Phần 15.

#Đề bài

  1. Xuất ra cả hai dạng thư viện

    libmath.a và libmath.so.1.0.0 kèm hai liên kết tượng trưng, từ cùng một bộ nguồn.

  2. Chỉ xuất ra đúng API trong tài liệu

    Kiểm tra bằng nm -D: số ký hiệu phải bằng đúng số hàm trong header.

  3. Đặt SONAME và số phiên bản

    Kèm macro phiên bản trong header và hàm phiên bản trong thư viện, để phát hiện lệch bản lúc chạy.

  4. Cài đặt và gỡ cài đặt được

    Tôn trọng DESTDIR và PREFIX, để trình đóng gói của bản phân phối dùng được.

  5. Cung cấp tệp pkg-config

    Để người dùng không phải đoán cờ dịch và thư viện phụ thuộc.

#Cây thư mục và header

Cây thư mục
mathlib/
  include/
    mathlib.h          API cong khai
    mathlib_api.h      macro ML_API cho tung nen tang
  src/
    hinhhoc.c
    sohoc.c
    noibo.h            khai bao dung chung TRONG thu vien, khong cai dat
    noibo.c
  test/
    test.h
    test.c
    chay_thu.c
  mathlib.pc.in        mau tep pkg-config
  Makefile
  README.md
include/mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H

#include "mathlib_api.h"

#ifdef __cplusplus
extern "C" {
#endif

/* --- phien ban --- */

#define ML_PHIEN_BAN_CHINH  1
#define ML_PHIEN_BAN_PHU    0
#define ML_PHIEN_BAN_VA     0

#define ML_PHIEN_BAN \
    (ML_PHIEN_BAN_CHINH * 10000 + ML_PHIEN_BAN_PHU * 100 + ML_PHIEN_BAN_VA)

#define ML_PHIEN_BAN_CHUOI "1.0.0"

ML_API unsigned    ml_phien_ban(void);
ML_API const char *ml_phien_ban_chuoi(void);

/* --- hang so, dua thanh HAM de sua duoc ma khong dich lai --- */

ML_API double ml_pi(void);

/* --- hinh hoc --- */

ML_API double ml_dt_hinh_tron(double r);
ML_API double ml_cv_hinh_tron(double r);

/* --- so hoc --- */

ML_API long ml_giai_thua(int n);
ML_API int  ml_ucln(int a, int b);

#ifdef __cplusplus
}
#endif

#endif
src/noibo.h, header nội bộ không cài đặt
#ifndef ML_NOIBO_H
#define ML_NOIBO_H

#include "mathlib_api.h"

/* Ham dung chung giua cac tep .c cua thu vien, nhung KHONG lo ra ngoai.
   static khong dung duoc vi can nhieu tep, nen dung ML_NOI_BO. */

ML_NOI_BO int ml_noi_bo_kiem_tra_am(int x);
ML_NOI_BO double ml_noi_bo_lam_tron(double x, int chu_so);

#endif

/* Tep nay nam trong src/, KHONG nam trong include/, va KHONG duoc
   cai dat. Do la ranh gioi vat ly giua API va cai dat. */

#Makefile

Makefile
# ===== thong tin thu vien =====
TEN       = math
CHINH     = 1
PHU       = 0
VA        = 0
PHIEN_BAN = $(CHINH).$(PHU).$(VA)

# ===== duong dan cai dat =====
PREFIX    ?= /usr/local
LIBDIR    ?= $(PREFIX)/lib
INCDIR    ?= $(PREFIX)/include
PCDIR     ?= $(LIBDIR)/pkgconfig
DESTDIR   ?=

# ===== cong cu =====
CC        ?= gcc
AR        ?= ar
CFLAGS    ?= -O2
CHUNG     = -std=c11 -Iinclude -Isrc \
            -Wall -Wextra -Wpedantic -Wshadow -Wconversion \
            -Wstrict-prototypes -Wmissing-prototypes

# ===== tep =====
NGUON     = $(wildcard src/*.c)
O_TINH    = $(patsubst src/%.c,build/tinh/%.o,$(NGUON))
O_DONG    = $(patsubst src/%.c,build/dong/%.o,$(NGUON))

A         = build/lib$(TEN).a
SO_DAY    = build/lib$(TEN).so.$(PHIEN_BAN)
SO_NAME   = lib$(TEN).so.$(CHINH)
SO_LINK   = lib$(TEN).so

all: $(A) $(SO_DAY) mathlib.pc

# ===== thu vien tinh =====
$(A): $(O_TINH)
	$(AR) rcs $@ $^

build/tinh/%.o: src/%.c | build/tinh
	$(CC) $(CHUNG) $(CFLAGS) -DMATHLIB_STATIC -c -o $@ $<

# ===== thu vien dong =====
$(SO_DAY): $(O_DONG)
	$(CC) -shared -Wl,-soname,$(SO_NAME) $^ -o $@
	ln -sf lib$(TEN).so.$(PHIEN_BAN) build/$(SO_NAME)
	ln -sf $(SO_NAME) build/$(SO_LINK)

build/dong/%.o: src/%.c | build/dong
	$(CC) $(CHUNG) $(CFLAGS) -DMATHLIB_BUILD -fPIC -fvisibility=hidden \
	      -c -o $@ $<

build/tinh build/dong:
	mkdir -p $@

# ===== pkg-config =====
mathlib.pc: mathlib.pc.in
	sed -e 's|@PREFIX@|$(PREFIX)|g' \
	    -e 's|@LIBDIR@|$(LIBDIR)|g' \
	    -e 's|@INCDIR@|$(INCDIR)|g' \
	    -e 's|@PHIEN_BAN@|$(PHIEN_BAN)|g' \
	    -e 's|@TEN@|$(TEN)|g' $< > $@

# ===== kiem thu =====
thu: $(A)
	$(CC) $(CHUNG) -g3 -O1 -DMATHLIB_STATIC \
	      -fsanitize=address,undefined -fno-sanitize-recover=all \
	      test/*.c $(A) -o build/chay_thu
	./build/chay_thu

# ===== kiem tra ky hieu xuat ra =====
kiem_ky_hieu: $(SO_DAY)
	@echo "ky hieu xuat ra:"
	@nm -D --defined-only $(SO_DAY) | grep -c ' T ' || true
	@echo "ham trong header:"
	@grep -c '^ML_API' include/mathlib.h

.PHONY: all thu kiem_ky_hieu cai go_cai sach
terminal
make kiem_ky_hieu
ky hieu xuat ra:
7
ham trong header:
7

#Cài đặt vào hệ thống

Phần cài đặt của Makefile
cai: all
	install -d $(DESTDIR)$(LIBDIR)
	install -d $(DESTDIR)$(INCDIR)
	install -d $(DESTDIR)$(PCDIR)

	install -m 644 $(A)      $(DESTDIR)$(LIBDIR)/
	install -m 755 $(SO_DAY) $(DESTDIR)$(LIBDIR)/
	ln -sf lib$(TEN).so.$(PHIEN_BAN) $(DESTDIR)$(LIBDIR)/$(SO_NAME)
	ln -sf $(SO_NAME)                $(DESTDIR)$(LIBDIR)/$(SO_LINK)

	install -m 644 include/mathlib.h     $(DESTDIR)$(INCDIR)/
	install -m 644 include/mathlib_api.h $(DESTDIR)$(INCDIR)/
	install -m 644 mathlib.pc            $(DESTDIR)$(PCDIR)/

	@echo "Da cai. Chay 'ldconfig' neu cai vao duong dan he thong."

go_cai:
	rm -f $(DESTDIR)$(LIBDIR)/lib$(TEN).a
	rm -f $(DESTDIR)$(LIBDIR)/lib$(TEN).so*
	rm -f $(DESTDIR)$(INCDIR)/mathlib.h
	rm -f $(DESTDIR)$(INCDIR)/mathlib_api.h
	rm -f $(DESTDIR)$(PCDIR)/mathlib.pc

sach:
	rm -rf build mathlib.pc
BiếnAi đặtDùng làm gì
PREFIXNgười dùngCài đi đâu. Mặc định /usr/local, hệ thống dùng /usr
DESTDIRTrình đóng góiThư mục dàn dựng tạm khi tạo gói, KHÔNG vào đường dẫn trong tệp
LIBDIRNgười dùngMột số hệ dùng lib64 thay vì lib

#pkg-config

mathlib.pc.in
prefix=@PREFIX@
exec_prefix=${prefix}
libdir=@LIBDIR@
includedir=@INCDIR@

Name: mathlib
Description: Thu vien toan hoc co ban
URL: https://vi_du/mathlib
Version: @PHIEN_BAN@
Libs: -L${libdir} -l@TEN@
Libs.private: -lm
Cflags: -I${includedir}
terminal
pkg-config --cflags mathlib
-I/usr/local/include
pkg-config --libs mathlib
-L/usr/local/lib -lmath
pkg-config --libs --static mathlib
-L/usr/local/lib -lmath -lm
pkg-config --modversion mathlib
1.0.0
gcc app.c $(pkg-config --cflags --libs mathlib) -o app
TrườngNghĩa
NameTên hiển thị, không phải tên dùng để tra
VersionCho phép người dùng yêu cầu bản tối thiểu
LibsCờ liên kết, luôn cần
Libs.privateCờ chỉ cần khi liên kết TĨNH, ví dụ -lm
CflagsCờ dịch, thường chỉ có -I
RequiresThư viện khác cũng có tệp .pc, ví dụ zlib >= 1.2
Requires.privateNhư trên nhưng chỉ cần khi liên kết tĩnh
Người dùng thư viện của bạn viết Makefile thế nào
CFLAGS  += $(shell pkg-config --cflags mathlib)
LDLIBS  += $(shell pkg-config --libs mathlib)

app: main.o
	$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LDLIBS)

# Nho: LDLIBS o CUOI, theo dung quy tac thu tu cua Bai 39.2.

# Va kiem tra ngay dau Makefile cho thong bao ro rang:
ifeq ($(shell pkg-config --exists mathlib && echo co),)
$(error Khong tim thay mathlib. Cai no hoac dat PKG_CONFIG_PATH)
endif

#Danh sách nghiệm thu

Thư viện tĩnh

  • ar t liệt kê đúng số tệp .o mong đợi.
  • nm chỉ hiện chữ T hoa ở những hàm trong header.
  • Chương trình liên kết với nó chạy được sau khi xóa tệp .a đi.
  • Đặt tên đúng quy ước để -lmath tìm ra.

Thư viện động

  • Có SONAME, kiểm tra bằng objdump -p | grep SONAME.
  • Đủ ba mức tên và hai liên kết tượng trưng.
  • Chương trình phụ thuộc vào SONAME chứ không vào tên liên kết tượng trưng.
  • Sửa ml_pi, dịch lại chỉ thư viện, chương trình cũ cho kết quả mới.

Ký hiệu và ABI

  • Số ký hiệu xuất ra bằng đúng số hàm ML_API trong header.
  • Mọi tên công khai có tiền tố ml_.
  • Header có extern "C" và dịch được bằng g++.
  • Không macro hằng số nào trong header công khai ngoài số phiên bản.
  • Có cả macro ML_PHIEN_BAN lẫn hàm ml_phien_ban().

Đóng gói

  • make PREFIX=... DESTDIR=... cai chạy đúng, và DESTDIR không lọt vào nội dung tệp nào.
  • make go_cai xóa sạch mọi thứ đã cài.
  • pkg-config --cflags --libs mathlib cho ra cờ dùng được ngay.
  • Libs.private có -lm, và Libs thì không.
  • make sach && make && make thu chạy từ đầu và cho ra 0 hỏng.

Tự làm thử

  1. Dựng đủ cây thư mục và chạy make ra cả hai dạng thư viện.
  2. Chạy make kiem_ky_hieu và làm cho hai số bằng nhau.
  3. Thêm một hàm không đánh dấu ML_API và xác nhận kiểm tra ký hiệu phát hiện ra.
  4. Cài vào $HOME/.local và dịch một chương trình bằng pkg-config.
  5. Cài với DESTDIR và kiểm tra nó không lọt vào tệp .pc.
  6. Sửa ml_pi, dịch lại chỉ thư viện động, chạy lại chương trình cũ.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Phải dịch hai lần vào hai thư mục khác nhau: bản tĩnh không có dllexport và không cần -fPIC, bản động thì ngược lại.
  • So số ký hiệu xuất ra với số hàm trong header là bài kiểm tra rẻ nhất giữ ranh giới API khỏi trôi.
  • PREFIX là nơi thư viện sẽ nằm khi chạy; DESTDIR là thư mục dàn dựng tạm và không bao giờ được lọt vào nội dung tệp.
  • Libs cho liên kết động, Libs.private cho những thứ chỉ cần khi liên kết tĩnh.
  • Header nội bộ nằm trong src/ chứ không trong include/, đó là ranh giới vật lý giữa API và cài đặt.