Libtool-bin:翻译官的工具箱使用手册
引子翻译官报到但他的工具箱在哪你听说了Libtool的大名——那个精通三十种操作系统方言的翻译官。你迫不及待地想请他来帮忙编译你的库。你打开终端信心满满地敲下$ libtool--modecompile gcc-cfoo.c bash: libtool:commandnot found翻译官不在。你去找他。发现他分成了两个包裹寄过来的libtool → 翻译官的知识宏定义、M4脚本、配置逻辑 libtool-bin → 翻译官的身体真正可以执行的命令行工具光有知识没有身体翻译官只是一本字典。光有身体没有知识翻译官只是一个哑巴。libtool-bin就是那个能站起来、走到工地上、张嘴说话的翻译官本人。今天我们就来详细讲解这个翻译官的使用手册——他能做什么怎么指挥他以及每个指令背后发生了什么。第一章安装——把翻译官请进门Debian/Ubuntu# 安装libtool-bin翻译官的身体$sudoaptinstalllibtool-bin# 这会同时安装libtool翻译官的知识作为依赖# 安装完成后你会得到这些工具# /usr/bin/libtool ← 主命令# /usr/bin/libtoolize ← 项目初始化工具CentOS/RHEL/Fedora# 在Red Hat系列中libtool和libtool-bin没有分开$sudoyuminstalllibtool# 或$sudodnfinstalllibtoolmacOS# 用Homebrew安装$ brewinstalllibtool# 注意macOS自带一个叫libtool的工具但那是苹果自己的不是GNU libtool# Homebrew安装的GNU版本通常叫 glibtool 和 glibtoolize$whichglibtool /usr/local/bin/glibtool验证安装$ libtool--versionlibtool(GNU libtool)2.4.7 Written by Gordon Matzigkeit,1996$ libtoolize--versionlibtoolize(GNU libtool)2.4.7 $whichlibtool /usr/bin/libtool翻译官到岗了。让我们看看他的工具箱里有什么。第二章libtool-bin的两个命令libtool-bin包含两个可执行文件各司其职┌─────────────────────────────────────────────────────┐ │ libtool-bin 工具箱 │ ├──────────────┬──────────────────────────────────────┤ │ │ │ │ libtool │ 翻译官本人 │ │ │ 负责编译、链接、安装、清理库文件 │ │ │ 有五种工作模式 │ │ │ │ ├──────────────┼──────────────────────────────────────┤ │ │ │ │ libtoolize │ 翻译官的秘书 │ │ │ 负责在你的项目中安插翻译官 │ │ │ 把Libtool的支持文件复制到你的项目里 │ │ │ │ └──────────────┴──────────────────────────────────────┘我们先从秘书开始。第三章libtoolize——在你的项目中安插翻译官什么时候用当你从零开始创建一个使用Autotools的项目时你需要先运行libtoolize让它把Libtool的支持文件复制到你的项目目录中。实际操作假设你有一个新项目mylib/ ├── src/ │ ├── parser.c │ ├── parser.h │ ├── lexer.c │ ├── lexer.h │ ├── utils.c │ └── utils.h ├── configure.ac └── Makefile.am你的configure.ac里写了LT_INIT告诉Autoconf你要用Libtool# configure.ac AC_INIT([mylib], [1.0.0]) AM_INIT_AUTOMAKE([foreign]) LT_INIT # ← 这一行表示我需要翻译官 AC_PROG_CC AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT现在运行libtoolize$ libtoolize--copy--forcelibtoolize: putting auxiliary filesin..libtoolize: copyingfile./ltmain.shlibtoolize: putting macrosinAC_CONFIG_MACRO_DIRS,m4.libtoolize: copyingfilem4/libtool.m4libtoolize: copyingfilem4/ltoptions.m4libtoolize: copyingfilem4/ltsugar.m4libtoolize: copyingfilem4/ltversion.m4libtoolize: copyingfilem4/lt~obsolete.m4翻译官的秘书做了什么她往你的项目里塞了一堆文件mylib/ ├── src/ │ ├── parser.c │ ├── ... ├── m4/ ← 新建的目录 │ ├── libtool.m4 ← Libtool的Autoconf宏定义 │ ├── ltoptions.m4 ← 选项处理宏 │ ├── ltsugar.m4 ← 语法糖宏 │ ├── ltversion.m4 ← 版本信息宏 │ └── lt~obsolete.m4 ← 废弃宏的兼容层 ├── ltmain.sh ← 翻译官的大脑核心脚本 ├── configure.ac └── Makefile.amltmain.sh是最重要的文件——它有一万多行Shell脚本包含了Libtool在所有平台上的行为逻辑。当configure运行时它会读取ltmain.sh结合当前平台的检测结果生成一个定制化的libtool脚本。ltmain.sh通用模板 configure的检测结果 libtool定制化脚本libtoolize的常用参数# 基本用法libtoolize# 复制文件而不是创建符号链接推荐用于发布的项目libtoolize--copy# 强制覆盖已有文件libtoolize--force# 同时复制和强制覆盖最常用的组合libtoolize--copy--force# 指定辅助文件的存放目录libtoolize--copy--force--automake# 查看会做什么但不实际执行libtoolize --dry-run# 安静模式libtoolize--quiet在autogen.sh中的位置大多数项目会把libtoolize放在autogen.sh脚本中#!/bin/sh# autogen.sh —— 项目的引导脚本echoRunning libtoolize...libtoolize--copy--force# 第一步安插翻译官echoRunning aclocal...aclocal-Im4# 第二步收集所有宏定义echoRunning autoheader...autoheader# 第三步生成config.h模板echoRunning automake...automake --add-missing--copy# 第四步生成Makefile.inechoRunning autoconf...autoconf# 第五步生成configureechoDone. Now run ./configure顺序很重要——libtoolize必须在aclocal之前运行因为aclocal需要读取libtoolize复制过来的.m4宏文件。第四章libtool——翻译官的五种工作模式libtool命令有五种工作模式每种模式对应构建过程中的一个阶段┌──────────────────────────────────────────────────────────┐ │ libtool 的五种模式 │ ├──────────┬───────────────────────────────────────────────┤ │ compile │ 编译源文件 → 生成 .lo 对象文件 │ │ link │ 链接对象文件 → 生成 .la 库文件或可执行文件 │ │ install │ 安装库文件到系统目录 │ │ execute │ 在正确的环境中运行未安装的程序 │ │ clean │ 清理Libtool生成的所有文件 │ │ uninstall│ 从系统目录卸载库文件 │ │ finish │ 完成库的安装运行ldconfig等 │ └──────────┴───────────────────────────────────────────────┘让我们逐个深入。模式一compile——把源码变成砖块$ libtool--modecompile gcc-csrc/parser.c-osrc/parser.lo你说的话“把parser.c编译成目标文件。”翻译官听到后做的事# 翻译官的内心独白# compile模式。让我看看当前平台需要什么...# Linux平台。共享库需要PIC代码静态库不需要。# 我得编译两次。# 第一次编译PIC版本给共享库用gcc-csrc/parser.c-fPIC-DPIC-osrc/.libs/parser.o# 第二次编译非PIC版本给静态库用gcc-csrc/parser.c-osrc/parser.o# 生成 .lo 描述文件catsrc/parser.loEOF # parser.lo - a libtool object file # Generated by libtool pic_object.libs/parser.o non_pic_objectparser.o EOF实际演示$ libtool--modecompile gcc-csrc/parser.c-osrc/parser.lo libtool: compile: gcc-csrc/parser.c-fPIC-DPIC-osrc/.libs/parser.o libtool: compile: gcc-csrc/parser.c-osrc/parser.o $lssrc/ parser.c parser.h parser.lo parser.o $lssrc/.libs/ parser.o看到了吗两个parser.o——一个在src/下非PIC一个在src/.libs/下PIC。parser.lo是一个文本文件记录了这两个文件的位置。编译多个文件$ libtool--modecompile gcc-csrc/parser.c-osrc/parser.lo $ libtool--modecompile gcc-csrc/lexer.c-osrc/lexer.lo $ libtool--modecompile gcc-csrc/utils.c-osrc/utils.lo带编译选项# 所有gcc的选项都可以正常使用$ libtool--modecompile gcc\-DHAVE_CONFIG_H\-I./include\-I/usr/include/glib-2.0\-O2-Wall-Wextra\-csrc/parser.c-osrc/parser.lo# Libtool会把这些选项原封不动地传给gcc# 只是额外加上 -fPIC -DPIC在需要的平台上模式二link——把砖块砌成墙这是Libtool最复杂、最强大的模式。场景A创建共享库静态库$ libtool--modelink gcc-osrc/libmylib.la\src/parser.lo src/lexer.lo src/utils.lo\-rpath/usr/local/lib\-version-info2:1:0你说的话“把这三个目标文件链接成一个叫libmylib的库版本号2:1:0将来安装到/usr/local/lib。”翻译官做的事Linux上# 1. 创建共享库gcc-shared\-Wl,-soname -Wl,libmylib.so.2\-osrc/.libs/libmylib.so.2.0.1\src/.libs/parser.o src/.libs/lexer.o src/.libs/utils.o# 2. 创建符号链接(cd src/.libsln-sflibmylib.so.2.0.1 libmylib.so.2)(cd src/.libsln-sflibmylib.so.2 libmylib.so)# 3. 创建静态库ar cru src/.libs/libmylib.a\src/parser.o src/lexer.o src/utils.o ranlib src/.libs/libmylib.a# 4. 生成 .la 描述文件catsrc/libmylib.laEOF dlnamelibmylib.so.2 library_nameslibmylib.so.2.0.1 libmylib.so.2 libmylib.so old_librarylibmylib.a inherited_linker_flags dependency_libs current2 age0 revision1 installedno shouldnotlinkno libdir/usr/local/lib EOF实际输出$ libtool--modelink gcc-osrc/libmylib.la\src/parser.lo src/lexer.lo src/utils.lo\-rpath/usr/local/lib\-version-info2:1:0 libtool: link: gcc-shared-fPIC-DPIC\src/.libs/parser.o src/.libs/lexer.o src/.libs/utils.o\-Wl,-soname -Wl,libmylib.so.2\-osrc/.libs/libmylib.so.2.0.1 libtool: link:(cd src/.libsrm-flibmylib.so.2\ln-slibmylib.so.2.0.1 libmylib.so.2)libtool: link:(cd src/.libsrm-flibmylib.so\ln-slibmylib.so.2 libmylib.so)libtool: link: ar cru src/.libs/libmylib.a\src/parser.o src/lexer.o src/utils.o libtool: link: ranlib src/.libs/libmylib.a $lssrc/.libs/ libmylib.a libmylib.so -libmylib.so.2 libmylib.so.2 -libmylib.so.2.0.1 libmylib.so.2.0.1 parser.o lexer.o utils.o场景B链接可执行文件$ libtool--modelink gcc-omyapp main.o src/libmylib.la翻译官做的事# 1. 链接可执行文件gcc-o.libs/myapp main.o\-Lsrc/.libs-lmylib\-Wl,-rpath -Wl,/usr/local/lib# 2. 生成包装脚本用于在安装前运行程序catmyappWRAPPER #!/bin/sh # 设置库搜索路径 LD_LIBRARY_PATH/home/user/mylib/src/.libs:$LD_LIBRARY_PATH export LD_LIBRARY_PATH # 运行真正的程序 exec /home/user/mylib/.libs/myapp $ WRAPPERchmodx myapp$ls-lamyapp -rwxr-xr-x1user user1234Jun1510:30 myapp ← 包装脚本 $ls-la.libs/myapp -rwxr-xr-x1user user45678Jun1510:30 .libs/myapp ← 真正的程序 $filemyapp myapp: POSIX shell script, ASCII text executable $file.libs/myapp .libs/myapp: ELF64-bit LSB executable, x86-64关键参数详解libtool--modelink gcc-olibfoo.la foo.lo bar.lo\-rpath/usr/local/lib\# 安装路径必须指定-version-info3:2:1\# 版本号 Current:Revision:Age-lpthread-ldl\# 依赖的系统库-L/opt/glib/lib -lglib-2.0\# 依赖的第三方库-no-undefined\# 不允许未定义符号Windows需要-export-symbols-regex^mono_# 只导出以mono_开头的符号-rpath的重要性# 有 -rpath生成共享库 静态库libtool--modelink gcc-olibfoo.la foo.lo-rpath/usr/local/lib# → 生成 .libs/libfoo.so 和 .libs/libfoo.a# 没有 -rpath只生成静态库libtool--modelink gcc-olibfoo.la foo.lo# → 只生成 .libs/libfoo.a# Libtool认为你没指定安装路径说明这个库不打算安装# 不安装的库没必要做成共享库。这是很多新手踩的坑——为什么我的共享库没有生成多半是忘了-rpath。-version-info C:R:A的版本号规则C Current 当前接口版本号 R Revision 当前接口的修订号 A Age 向后兼容的接口数量 更新规则 - 只修了Bug接口没变 → C:R1:A 只增加修订号 - 新增了接口旧接口没变 → C1:0:A1 增加当前版本和兼容数 - 修改或删除了旧接口 → C1:0:0 增加当前版本重置兼容数 示例 1:0:0 → 修Bug → 1:1:0 → 加接口 → 2:0:1 → 改接口 → 3:0:0模式三install——把墙板装到房子里$sudolibtool--modeinstallinstall-csrc/libmylib.la /usr/local/lib/翻译官做的事# 1. 安装共享库install-csrc/.libs/libmylib.so.2.0.1 /usr/local/lib/# 2. 创建符号链接(cd /usr/local/libln-sflibmylib.so.2.0.1 libmylib.so.2)(cd /usr/local/libln-sflibmylib.so.2 libmylib.so)# 3. 安装静态库install-csrc/.libs/libmylib.a /usr/local/lib/ ranlib /usr/local/lib/libmylib.a# 4. 安装.la文件更新路径信息install-csrc/libmylib.la /usr/local/lib/# 并把 installedno 改为 installedyes# 把相对路径改为绝对路径# 5. 更新系统库缓存ldconfig /usr/local/lib安装可执行文件$sudolibtool--modeinstallinstall-cmyapp /usr/local/bin/# 这次安装的是 .libs/myapp真正的二进制文件# 不是那个包装脚本install-c.libs/myapp /usr/local/bin/myapp模式四execute——在正确的环境中运行$ libtool--modeexecute gdb ./myapp翻译官做的事# 设置 LD_LIBRARY_PATH 指向 .libs/ 目录# 然后在这个环境中运行 gdbLD_LIBRARY_PATH/home/user/mylib/src/.libs:$LD_LIBRARY_PATH\gdb /home/user/mylib/.libs/myapp这在调试时特别有用——你想用gdb、valgrind、strace等工具分析你的程序但这些工具不认识Libtool的包装脚本。execute模式帮你设置好环境然后直接运行真正的二进制文件。# 用valgrind检查内存泄漏$ libtool--modeexecute valgrind --leak-checkfull ./myapp# 用strace跟踪系统调用$ libtool--modeexecutestrace./myapp# 用ldd查看动态库依赖$ libtool--modeexecute ldd .libs/myapp linux-vdso.so.1(0x00007ffd...)libmylib.so.2/home/user/mylib/src/.libs/libmylib.so.2 libglib-2.0.so.0/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0 libc.so.6/lib/x86_64-linux-gnu/libc.so.6模式五clean——打扫战场$ libtool--modecleanrm-fsrc/libmylib.la src/parser.lo src/lexer.lo src/utils.lo翻译官做的事# 删除 .lo 文件rm-fsrc/parser.lo src/lexer.lo src/utils.lo# 删除 .libs/ 下的对应文件rm-fsrc/.libs/parser.o src/.libs/lexer.o src/.libs/utils.o# 删除非PIC目标文件rm-fsrc/parser.o src/lexer.o src/utils.o# 删除 .la 文件rm-fsrc/libmylib.la# 删除 .libs/ 下的库文件rm-fsrc/.libs/libmylib.so*rm-fsrc/.libs/libmylib.a# 如果 .libs/ 目录空了删除它rmdirsrc/.libs/2/dev/null普通的rm只会删除你指定的文件。但Libtool知道每个.lo和.la文件背后还藏着一堆真正的二进制文件在.libs/里。clean模式确保所有相关文件都被清理干净。第五章完整实战——从零构建一个库让我们把所有知识串起来从零开始构建一个完整的库项目。# # 第一步创建项目结构# mkdir-pmylib/src mylib/m4cdmylib# 写源码catsrc/calculator.cEOF #include calculator.h int calc_add(int a, int b) { return a b; } int calc_sub(int a, int b) { return a - b; } int calc_mul(int a, int b) { return a * b; } EOFcatsrc/calculator.hEOF #ifndef CALCULATOR_H #define CALCULATOR_H int calc_add(int a, int b); int calc_sub(int a, int b); int calc_mul(int a, int b); #endif EOFcatsrc/main.cEOF #include stdio.h #include calculator.h int main() { printf(3 4 %d\n, calc_add(3, 4)); printf(10 - 3 %d\n, calc_sub(10, 3)); printf(6 * 7 %d\n, calc_mul(6, 7)); return 0; } EOF# # 第二步不用Autotools纯手动使用libtool# # 编译libtool--modecompile gcc-csrc/calculator.c-osrc/calculator.lo# 链接成库libtool--modelink gcc-osrc/libcalculator.la\src/calculator.lo\-rpath/usr/local/lib\-version-info1:0:0# 编译主程序gcc-csrc/main.c-osrc/main.o-Isrc# 链接主程序引用Libtool库libtool--modelink gcc-ocalcapp\src/main.o src/libcalculator.la# 运行通过包装脚本./calcapp# 输出# 3 4 7# 10 - 3 7# 6 * 7 42# 安装sudolibtool--modeinstallinstall-c\src/libcalculator.la /usr/local/lib/sudolibtool--modeinstallinstall-c\calcapp /usr/local/bin/# 安装后运行/usr/local/bin/calcapp# 输出相同但这次不需要包装脚本了# 清理libtool--modecleanrm-fsrc/calculator.lo src/libcalculator.la尾声翻译官的日常每天在全世界无数的构建服务器上libtool被调用几百万次。每一次调用它都在做同样的事情——听你说一句简单的话然后翻译成当前平台能理解的复杂命令。你说--modecompile它编译两次生成PIC和非PIC两个版本。你说--modelink它创建共享库、静态库、符号链接、描述文件。你说--modeinstall它复制文件、更新路径、刷新缓存。你说--modeexecute它设置环境、启动程序。你说--modeclean它追踪每一个隐藏的文件清理得干干净净。它从不抱怨平台的差异有多荒谬。它从不吐槽AIX把共享库塞进.a文件有多离谱。它从不嘲笑HP-UX用.sl作为扩展名有多奇葩。它只是安静地翻译准确地执行然后把舞台让给你的代码。下次你在编译日志中看到那些libtool: compile:和libtool: link:的输出时请记住——那是翻译官在工作。他正拿着他的工具箱站在你和操作系统之间把混乱翻译成秩序。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2433818.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!