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
Xuất ra cả hai dạng thư viện
libmath.avàlibmath.so.1.0.0kèm hai liên kết tượng trưng, từ cùng một bộ nguồn.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.Đặ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.
Cài đặt và gỡ cài đặt được
Tôn trọng
DESTDIRvàPREFIX, để trình đóng gói của bản phân phối dùng được.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.mdinclude/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
#endifsrc/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 sachterminal
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ến | Ai đặt | Dùng làm gì |
|---|---|---|
| PREFIX | Người dùng | Cài đi đâu. Mặc định /usr/local, hệ thống dùng /usr |
| DESTDIR | Trình đóng gói | Thư mục dàn dựng tạm khi tạo gói, KHÔNG vào đường dẫn trong tệp |
| LIBDIR | Người dùng | Mộ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ường | Nghĩa |
|---|---|
| Name | Tên hiển thị, không phải tên dùng để tra |
| Version | Cho phép người dùng yêu cầu bản tối thiểu |
| Libs | Cờ liên kết, luôn cần |
| Libs.private | Cờ chỉ cần khi liên kết TĨNH, ví dụ -lm |
| Cflags | Cờ dịch, thường chỉ có -I |
| Requires | Thư viện khác cũng có tệp .pc, ví dụ zlib >= 1.2 |
| Requires.private | Như 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 tliệt kê đúng số tệp.omong đợi.nmchỉ hiện chữThoa ở 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 để
-lmathtì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_APItrong header. - Mọi tên công khai có tiền tố
ml_. - Header có
extern "C"và dịch được bằngg++. - 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_BANlẫn hàmml_phien_ban().
Đóng gói
make PREFIX=... DESTDIR=... caichạy đúng, vàDESTDIRkhông lọt vào nội dung tệp nào.make go_caixóa sạch mọi thứ đã cài.pkg-config --cflags --libs mathlibcho ra cờ dùng được ngay.Libs.privatecó-lm, vàLibsthì không.make sach && make && make thuchạy từ đầu và cho ra 0 hỏng.
Tự làm thử
- Dựng đủ cây thư mục và chạy
makera cả hai dạng thư viện. - Chạy
make kiem_ky_hieuvà làm cho hai số bằng nhau. - Thêm một hàm không đánh dấu
ML_APIvà xác nhận kiểm tra ký hiệu phát hiện ra. - Cài vào
$HOME/.localvà dịch một chương trình bằngpkg-config. - Cài với
DESTDIRvà kiểm tra nó không lọt vào tệp.pc. - 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ó
dllexportvà 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.
PREFIXlà nơi thư viện sẽ nằm khi chạy;DESTDIRlà thư mục dàn dựng tạm và không bao giờ được lọt vào nội dung tệp.Libscho liên kết động,Libs.privatecho những thứ chỉ cần khi liên kết tĩnh.- Header nội bộ nằm trong
src/chứ không tronginclude/, đó là ranh giới vật lý giữa API và cài đặt.