我的风格其实极其诡异,因为我只想讲我乐意讲的,但保证实用性,除了第一次的类hello world,全部脚本都在实际项目中使用(当然很多是fcitx的 😛 )。学习曲线什么的,谁管啊。
如果你想找前面的内容,请猛击 https://www.csslayer.info/wordpress/category/cmake/
顺带一提,本人近期一直占据oholh cmake 语言提交 http://www.ohloh.net/languages/74 排行中的一名。(有段生猛的时间也挤进过C语言的排名……不过现在出来了 :P)
本次大容量,包括三个内容,创建自己的可以被find_package使用的CMake脚本,复杂自定义文件生成,Gettext整合。
1、创建自己的可以被find_package使用的CMake脚本
用过 autotools的,如果你没用过 pkg-config,那说明你经验不够丰富……(好吧)
pkg-config是*nix上广泛使用的用于查找库,头文件的统一管理的工具。当然能力有限。下面来介绍一下如何在CMake中引用其他的库文件。
首先出场的就是 find_package 命令。find_package 一般使用方式为
find_package(PackageName [REQUIRED])
按照具体的包的不同还可以添加参数。CMake内置了不少库的查找方式,例如Libxml,X11,Qt,但远远不能满足人们的需求。但CMake也有PkgConfig的支持。下面以寻找glib为例。
如果你仅仅是在自己的项目内使用,你可以创建一个文件夹,比如叫做cmake,然后加入以下命令:
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
从而让cmake寻找find_package的脚本的时候也会使用你自己的脚本。脚本的名称为 Find<PackageName>.cmake
如果你想让系统中其他的cmake使用你自己的脚本,就像创建pkg-config一样,那么你可以创建一个名为<PackageName>Config.cmake 的脚本,并安装到 /usr/share/cmake 下面。
# - Try to find the GLIB2 libraries # Once done this will define # # GLIB2_FOUND - system has glib2 # GLIB2_INCLUDE_DIR - the glib2 include directory # GLIB2_LIBRARIES - glib2 library # Copyright (c) 2008 Laurent Montel, <montel@kde.org> # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) # Already in cache, be silent set(GLIB2_FIND_QUIETLY TRUE) endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) find_package(PkgConfig) pkg_check_modules(PC_LibGLIB2 QUIET glib-2.0) find_path(GLIB2_MAIN_INCLUDE_DIR NAMES glib.h HINTS ${PC_LibGLIB2_INCLUDEDIR} PATH_SUFFIXES glib-2.0) find_library(GLIB2_LIBRARY NAMES glib-2.0 HINTS ${PC_LibGLIB2_LIBDIR} ) set(GLIB2_LIBRARIES ${GLIB2_LIBRARY}) # search the glibconfig.h include dir under the same root where the library is found get_filename_component(glib2LibDir "${GLIB2_LIBRARIES}" PATH) find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h PATH_SUFFIXES glib-2.0/include HINTS ${PC_LibGLIB2_INCLUDEDIR} "${glib2LibDir}" ${CMAKE_SYSTEM_LIBRARY_PATH}) set(GLIB2_INCLUDE_DIR "${GLIB2_MAIN_INCLUDE_DIR}") # not sure if this include dir is optional or required # for now it is optional if(GLIB2_INTERNAL_INCLUDE_DIR) set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} "${GLIB2_INTERNAL_INCLUDE_DIR}") endif(GLIB2_INTERNAL_INCLUDE_DIR) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GLIB2 DEFAULT_MSG GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR) mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES)
脚本开头的第一件事就和c语言中的ifdef差不多,不要找两次。然后引用PkgConfig,从而使用PkgConfig相关的宏。接下来使用PkgConfig中的宏来找glib-2.0.pc,然后会生成以第一个参数为前缀的一系列变量。之后用find_library定位具体的库的位置。find_path来找一些额外头文件的位置。
最后采用FindPackageHandleStandardArgs里面的宏来显示一些默认信息。mark_as_advanced的目的是不在cmake-gui中显示某些变量。
当然你也可以直接在CMakeLists.txt 中使用PkgConfig宏包里面的命令,但创建可以复用的CMake脚本会更好。顺带一说,KDE在/usr/share/apps/cmake/下面有大量的其他库的find_package脚本。如果找不到的话也可以去那里找找。
2、复杂的自定义文件生成
我比较喜欢的方式是,用add_custom_command来指定文件的生成规则,用add_custom_target来确定是否要在make all中执行。
例如fcitx下载词库的部分。
add_custom_target(pinyin_data ALL DEPENDS ${PY_DATA}) add_custom_command(OUTPUT ${PY_ORGDATA} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/download.sh "${WGET}" "${PY_URL}" "${PY_TAR}" COMMAND ${TAR} xzmvf ${PY_TAR}) add_custom_command(OUTPUT ${PY_DATA} DEPENDS ${PY_ORGDATA} COMMAND createPYMB ${CMAKE_CURRENT_SOURCE_DIR}/gbkpy.org ${CMAKE_CURRENT_BINARY_DIR}/${PY_ORGDATA})
创建了一个名为 pinyin_data的target,DEPENDS后面指定了依赖的文件或者target。ALL表示总是执行。因为custom_target其实也可以指定输出,来实现按需编译。
然后是下载,下载这里我特意用了一个脚本来下载,因为我不想把下载的文件通过make clean清除掉。OUTPUT中的文件是会被make clean删除的。COMMAND可以指定多个命令,像脚本中那样。
第二个命令用来执行createPYMB,并且生成文件。
那么make的时候是个什么规则呢,首先是由于第一行脚本,make all也会触发make pinyin_data。然后make pinyin_data依赖的是${PY_DATA}的内容。于是${PY_DATA}就又会查看它所依赖的文件,${PY_ORGDATA}是否存在。没有则执行第一个custom_command,然后再执行第二个,从而完成pinyin_data这个target。
Lex,Yacc等可以通过类似方式(当然你可以自己写个宏,来简化命令)。
3、Gettext 整合
这年头,如果你的项目不支持国际化你都不好意思出来见人。那么用cmake来使用Gettext如何呢?
让我们从第一步开始。
1) 从po文件生成mo文件。
这是最简单的一步
首先引用cmake自带的Gettext支持。
find_package(Gettext REQUIRED)
然后在你的po目录下。
set(POT_FILE kcm_fcitx.pot ) file(GLOB PO_FILES *.po ) # Update .po files and compile them to binary .gmo files gettext_create_translations(${POT_FILE} ALL ${PO_FILES})
第一个是设定变量。
第二个是将文件名变成字符串变量,具体来说,就是满足*.po文件名的文件,变成PO_FILES这个变量的内容。前面的GLOB用于指定这个文件名匹配功能,当然还有很多其他的命令。这样最后就能将 *.mo 文件生成,并且install命令都可以省略了。
2) 自动提取源代码中的字符串到pot
我使用过的有两种风格,一种是KDE偏好的,一种是我自己编好的。KDE喜欢用的是使用Messages.sh 这个脚本来提取,而如果你本身就是KDE的项目,则会有机器人每天自动执行Messages.sh来提取。我在kcm-fcitx 中使用了这种风格,既然是shell脚本里面做什么就随便了:
https://github.com/fcitx/kcm-fcitx/blob/master/po/Messages.sh
可以自行修改来符合自身需要。
另外KDE给机器人使用的Messages.sh是不一样的,请自行留意。例如:
http://quickgit.kde.org/?p=kdeplasma-addons.git&a=blob&f=applets/kimpanel/src/Messages.sh
我自己风格的就是完全按照fcitx的需要设计的,因为fcitx需要提取的字符串更多更复杂。另外我这种方式也更适合使用intltool的人来迁移。
http://code.google.com/p/fcitx/source/browse/po/POTFILES.in.in
由于intltool本身的限制,所以里面使用了比较复杂的trick。
file(RELATIVE_PATH REL_SOURCE_ROOT ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}) if ("${REL_SOURCE_ROOT}" STREQUAL "") set(REL_SOURCE_ROOT ".") endif("${REL_SOURCE_ROOT}" STREQUAL "") set(POT_FILE fcitx.pot) configure_file(POTFILES.in.in ${CMAKE_CURRENT_BINARY_DIR}/POTFILES.in) extract_fcitx_addon_conf_postring() add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/desc.po COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/getdescpo ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} DEPENDS getdescpo) add_custom_target( pot COMMAND INTLTOOL_EXTRACT=${INTLTOOL_EXTRACT} srcdir=${CMAKE_CURRENT_BINARY_DIR} ${INTLTOOL_UPDATE} --gettext-package fcitx --pot WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/conf.po ${CMAKE_CURRENT_BINARY_DIR}/desc.po )
首先是获得项目编译目录和源码目录的“相对路径”。并将其填入REL_SOURCE_ROOT中,如果是同一个目录那么改成 “.”。
其次使用configure_file命令生成全部是相对路径的POTFILES.in,configure_file的使用参见第二次的Tutorial。
然后是一个fcitx的宏,跳过。
然后是一个fcitx需要的target,跳过。
最后是一个target,由于它没有被人依赖,所以make all并不会执行它。它的目的是更新“源码目录”(并非编译目录)下的pot文件。
里面的INTLTOOL_EXTRACT是采用
FIND_PROGRAM(INTLTOOL_EXTRACT intltool-extract) FIND_PROGRAM(INTLTOOL_UPDATE intltool-update) FIND_PROGRAM(INTLTOOL_MERGE intltool-merge)
获得的。find_program可以找系统的Path下面的可执行文件,也可以用其他参数来指定搜索路径。
DEPENDS里面的内容可以忽略,依赖的其实就是刚刚跳过的两个命令的OUTPUT。
3) 用intltool 合并desktop 翻译
ADD_CUSTOM_COMMAND( OUTPUT ${outfile} COMMAND LC_ALL=C ${INTLTOOL_MERGE} -d -u ${PROJECT_SOURCE_DIR}/po ${infile} ${outfile} DEPENDS ${infile} )
默认指定了目录和格式。一个简单的CUSTOM_COMMAND,通过刚刚的学习,想必大家都知道这里做了什么事情吧。
4) 非Linux平台的gettext库的查找
用此脚本即可,方法参见本次第一节:
http://code.google.com/p/fcitx/source/browse/cmake/FindLibintl.cmake
很有用,学习了。感谢分享! ^^:)
果然已经基本看不懂了……要看懂这个貌似得有维护较大项目的经验……
文章里的链接还是老站的,要不要更新一下链接?