????????软件开发完成后对外发布后,通常需要将生成的库文件和头文件进行打包发布,供其他人安装使用,使用CMake进行构建的项目中,通常使用CPack进行软件包的打包和发布,本文将介绍如何通过CPack将软件包打包为.sh包发布,并替换默认的脚本文件,定制安装操作。
一、CPack简介
????????CPack是一款优秀的跨平台软件打包发布工具,支持丰富的打包发布格式,包括NSIS、RPM、DEB、TGZ和STGZ等。不同的操作系统下,有多种对应的CPack打包器,其中Windows平台一下一般选择通过NSIS打包,生成.exe可执行文件进行交互式安装。Linux下Ubuntu系统一般通过DEB打包,生成.deb文件(Ubuntn不支持rpm包,需要将rpm包转换为deb后再安装)。而RedHat系统下,可通过RPM打包器打包为.rpm包。对应不同操作系统,也可以通过CPack将软件打包为与平台无关的TGZ和ZIP等压缩包。
????????Windows下打包为.exe可执行文件进行安装,是可以支持寻找软件包安装目录,并可通过添加安装后处理脚本,自动添加环境变量等操作的,比较友好。Linux的.rpm包也是可以修改默认安装路径的,而.deb包的默认安装路径是/usr/local或者/usr,对于开发环境下多用户的服务器,会有很多不友好的地方,因此通过STZG打包器生成.sh包,进行交互式安装是一个比较好的替代方式。
? ? ? ? CPack打包哪些文件是依赖于CMake中的install操作的,CPack只是打包工具,不指定打包文件,CMake install请参考其他博客。
二、STGZ包简介
? ? ? ? Linux下的STGZ包后缀通常是.sh和.run,其结构是一样的,都是一个sh脚本和压缩包文件拼接在一起后生成的,sh脚本中定义了软件包安装的一些操作,包括校验压缩包,解压压缩包,将文件拷贝到对应目录和设置环境变量等等,可以根据软件包安装需要进行定义。压缩包通常就是软件包对应的头文件和库文件了。
cat install.sh software.tag.gz > stgz_install.sh
?三、通过CPack生成.sh包
? ? ? ? 在指定了install内容之后,可通过设置CPACK_GENERATOR为STGZ来指定STGZ生成器将软件打包为.sh文件。打包的其他设置也可以通过对应CPack变量来进行设置,具体可设置的变量和对应功能可查阅官方文档(CPack官方文档),特别需要注意的是,只有CPack对应的变量才能在STGZ打包器的sh脚本中读取到,其他CMake变量在sh脚本中无法获取,因此如果想要传递一些设置到sh脚本里,只能通过CPack变量传递。
set(CPACK_GENERATOR "STGZ")
set(CPACK_PACKAGING_INSTALL_PREFIX "/")
set(CPACK_PACKAGE_VENDOR "test")
set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}")
set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION "${CMAKE_PROJECT_NAME} library")
set(CPACK_PACKAGE_MAINTAINER "test")
set(CPACK_PACKAGE_HOMEPAGE_URL "http://xxxx.com")
set (TAR_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_HOST_SYSTEM_NAME}-any")
if (DEFINED CUDA_VERSION)
set (TAR_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_HOST_SYSTEM_NAME}-CUDA${CUDA_VERSION}")
elseif (DEFINED CUDAToolkit_VERSION)
set (TAR_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_HOST_SYSTEM_NAME}-CUDA${CUDAToolkit_VERSION}")
endif ()
set(CPACK_PACKAGE_FILE_NAME "${TAR_FILE_NAME}")
set(CPACK_PACKAGE_DIRECTORY package)
include(CPack)
?????????CPack有默认的sh脚本,不过sh脚本功能比较简单,只支持展示license以及是否添加次文件夹操作,难以满足实际需要,如下图glog的.sh包安装所示。
?四、替换CPack默认的sh脚本,定制安装操作
? ? ? ?CPack默认的sh安装脚本,功能过于简单,如果想要添加额外的安装操作,就需要替换掉CPack默认的sh安装脚本,并定制安装操作。只需要自己写一个shell脚本,并命名为CPack.STGZ_Header.sh.in,.in文件是CMake中的模板文件,模板文件中的通过@包裹的@xxx@模板变量会在CMake阶段替换实际的内容,前面也说到,sh脚本中只能获取到CPack的对应变量,而无法获取到CMake中的其他变量,这是需要特别注意的。通过CMake的CMAKE_MODULE_PATH将包含CPack.STGZ_Header.sh.in的路径设置为CMake的模块路径,引入脚本文件就可以替换掉默认的sh脚本文件,CPack就会使用你的sh脚本来生成sh包。
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules")
? ? ? ? 这里提供了一个安装脚本模板,可在此基础上进行修改,支持交互式选择安装目录,并自动添加环境变量,也可以通过命令行参数设置对应的参数。
#!/bin/bash
#
# NAME: @CPACK_PACKAGE_NAME@
# VER: @CPACK_PACKAGE_VERSION@
# PLAT: linux-64
# LINES: 323
# check shell
if ! echo "$0" | grep '\.sh$' > /dev/null; then
printf 'Please run using "bash" or "sh", but not "." or "source"\\n' >&2
return 1
fi
# Determine RUNNING_SHELL; if SHELL is non-zero use that.
if [ -n "$SHELL" ]; then
RUNNING_SHELL="$SHELL"
else
if [ "$(uname)" = "Darwin" ]; then
RUNNING_SHELL=/bin/bash
else
if [ -d /proc ] && [ -r /proc ] && [ -d /proc/$$ ] && [ -r /proc/$$ ] && [ -L /proc/$$/exe ] && [ -r /proc/$$/exe ]; then
RUNNING_SHELL=$(readlink /proc/$$/exe)
fi
if [ -z "$RUNNING_SHELL" ] || [ ! -f "$RUNNING_SHELL" ]; then
RUNNING_SHELL=$(ps -p $$ -o args= | sed 's|^-||')
case "$RUNNING_SHELL" in
*/*)
;;
default)
RUNNING_SHELL=$(which "$RUNNING_SHELL")
;;
esac
fi
fi
fi
# Some final fallback locations
if [ -z "$RUNNING_SHELL" ] || [ ! -f "$RUNNING_SHELL" ]; then
if [ -f /bin/bash ]; then
RUNNING_SHELL=/bin/bash
else
if [ -f /bin/sh ]; then
RUNNING_SHELL=/bin/sh
fi
fi
fi
if [ -z "$RUNNING_SHELL" ] || [ ! -f "$RUNNING_SHELL" ]; then
printf 'Unable to determine your shell. Please set the SHELL env. var and re-run\\n' >&2
exit 1
fi
THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd)
THIS_FILE=$(basename "$0")
THIS_PATH="$THIS_DIR/$THIS_FILE"
PREFIX=$HOME/software
UPREFIX=0
SETENV=0
DSETENV=0
FORCE=0
USAGE="
usage: $0 [options]
Installs @CPACK_PACKAGE_NAME@ @CPACK_PACKAGE_VERSION@
-h|--help print this help message and exit
-p|--prefix=<path> install prefix, defaults to $PREFIX, must not contain spaces
-e|--env automatic setup of environment variables
-d|--disable-env not automatic setup of environment variables
-f|--force clear folder if install prefix folder already exists
"
OPTS=$(getopt -o hp:edf -l help,prefix:,env,disable-env,force -- "$@" 2>/dev/null)
if [ ! $? ]; then
printf "%s\\n" "$USAGE"
exit 2
fi
eval set -- "$OPTS"
while true; do
case "$1" in
-h|--help)
printf "%s\\n" "$USAGE"
exit 2
;;
-f|--force)
FORCE=1
shift
;;
-p|--prefix)
PREFIX="$2"
UPREFIX=1
shift
shift
;;
-e|--env)
SETENV=1
shift
;;
-d|--disable-env)
DSETENV=1
shift
;;
--)
shift
break
;;
*)
printf "ERROR: did not recognize option '%s', please try -h\\n" "$1"
exit 1
;;
esac
done
# check tar
if ! tar --help >/dev/null 2>&1; then
printf "WARNING: tar does not appear to be installed this may cause problems below\\n" >&2
fi
# check system architecture type
if [ "$(uname -m)" != "x86_64" ]; then
printf "WARNING:\\n"
printf " Your operating system appears not to be 64-bit, but you are trying to\\n"
printf " install a 64-bit version of @CPACK_PACKAGE_NAME@.\\n"
printf " Are sure you want to continue the installation? [yes|no]\\n"
printf "[no] >>> "
read -r ans
if [ "$ans" != "yes" ] && [ "$ans" != "Yes" ] && [ "$ans" != "YES" ] && \
[ "$ans" != "y" ] && [ "$ans" != "Y" ]
then
printf "Aborting installation\\n"
exit 2
fi
fi
#check operating system type
if [ "$(uname)" != "Linux" ]; then
printf "WARNING:\\n"
printf " Your operating system does not appear to be Linux, \\n"
printf " but you are trying to install a Linux version of @CPACK_PACKAGE_NAME@.\\n"
printf " Are sure you want to continue the installation? [yes|no]\\n"
printf "[no] >>> "
read -r ans
if [ "$ans" != "yes" ] && [ "$ans" != "Yes" ] && [ "$ans" != "YES" ] && \
[ "$ans" != "y" ] && [ "$ans" != "Y" ]
then
printf "Aborting installation\\n"
exit 2
fi
fi
printf "\\n"
printf "Welcome to @CPACK_PACKAGE_NAME@ @CPACK_PACKAGE_VERSION@\\n"
printf "\\n"
printf "@CPACK_PACKAGE_NAME@ will now be installed into this location:\\n"
printf "%s\\n" "$PREFIX"
printf "\\n"
if [ "$UPREFIX" = "0" ]; then
printf " - Press ENTER to confirm the location\\n"
printf " - Press CTRL-C to abort the installation\\n"
printf " - Or specify a different location below\\n"
printf "\\n"
printf "[%s] >>> " "$PREFIX"
read -r user_prefix
if [ "$user_prefix" != "" ]; then
case "$user_prefix" in
*\ * )
printf "ERROR: Cannot install into directories with spaces\\n" >&2
exit 1
;;
*)
eval PREFIX="$user_prefix"
;;
esac
fi
fi
case "$PREFIX" in
*\ * )
printf "ERROR: Cannot install into directories with spaces\\n" >&2
exit 1
;;
esac
PREFIX=$PREFIX/@CPACK_PACKAGE_NAME@
# confirm whether to overwrite
if [ "$FORCE" = "0" ] && [ -e "$PREFIX" ]; then
if [ "$(ls -A $PREFIX)" ]; then
printf "ERROR: File or directory is not empty: '%s'\\n" "$PREFIX" >&2
printf "Or use -f/--force to force the installation which will clear the original directory.\n" >&2
exit 1
fi
fi
if [ "$FORCE" = "1" ] && [ -e "$PREFIX" ]; then
if [ "$(ls -A $PREFIX)" ]; then
force_ans=no
printf "! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !\\n"
printf "Do you wish to clear the installation directory: '%s' ? [yes|no]\\n" "$PREFIX"
printf "\\n"
printf "[default is %s] >>> " "$force_ans"
read -r uans
if [ "$uans" != "" ]; then
force_ans=$uans
fi
if [ "$force_ans" = "yes" ] || [ "$force_ans" = "Yes" ] || [ "$force_ans" = "YES" ] || \
[ "$force_ans" = "y" ] || [ "$force_ans" = "Y" ]; then
rm -fr $PREFIX/*
printf "\\n"
printf "Finished to clear the installation directory: '%s'!\\n" "$PREFIX"
printf "\\n"
fi
fi
fi
# creat install directory
if ! mkdir -p "$PREFIX"; then
printf "ERROR: Could not create directory: '%s'\\n" "$PREFIX" >&2
exit 1
fi
PREFIX=$(cd "$PREFIX"; pwd)
export PREFIX
printf "install prefix = %s\\n" "$PREFIX"
# extract the tarball appended to this header, this creates the *.tar.gz files
# for all the packages which get installed below
cd "$PREFIX"
printf "Extracting, please wait...\\n"
if ! tail -n +323 "$THIS_PATH" | tar xzvf -; then
printf "ERROR: could not extract tar starting at line 323\\n" >&2
exit 1
fi
printf "installation finished.\\n"
BASH_RC="$HOME"/.bashrc
eans=no
ask=yes
if [ "$SETENV" = "1" ]; then
if [ "$DSETENV" = "1" ]; then
eans=no
ask=yes
else
eans=yes
ask=no
fi
else
if [ "$DSETENV" = "1" ]; then
eans=no
ask=no
else
eans=no
ask=yes
fi
fi
if [ "$ask" = "yes" ]; then
printf "Do you wish the installer to initialize @CPACK_PACKAGE_NAME@\\n"
printf "in your %s ? [yes|no]\\n" "$BASH_RC"
printf "[%s] >>> " "$eans"
read -r ans
if [ "$ans" != "" ]; then
eans=$ans
fi
fi
typeset -u PROJECT_UNAME
PROJECT_UNAME=@CPACK_PACKAGE_NAME@
if [ "$eans" != "yes" ] && [ "$eans" != "Yes" ] && [ "$eans" != "YES" ] && \
[ "$eans" != "y" ] && [ "$eans" != "Y" ]; then
printf "\\n"
printf "You may need to set it "$PROJECT_UNAME"_DIR manually in your $BASH_RC\\n"
printf "\\n"
printf "export "$PROJECT_UNAME"_DIR=\"$PREFIX/lib/cmake/@CPACK_PACKAGE_NAME@\"\\n"
printf "\\n"
printf "If it is a dynamic library, you also need to set LD_LIBRARY_PATH in your $BASH_RC\\n"
printf "\\n"
printf "export LD_LIBRARY_PATH=\"$PREFIX/lib:\$LD_LIBRARY_PATH\"\\n"
printf "\\n"
printf "Please source your $BASH_RC before include @CPACK_PACKAGE_NAME@ \\n"
printf "\\n"
else
printf "\\n"
printf "Initializing @CPACK_PACKAGE_NAME@ in %s\\n" "$BASH_RC"
printf "\\n"
addDir="export "$PROJECT_UNAME"_DIR=\"$PREFIX/lib/cmake/@CPACK_PACKAGE_NAME@\""
if ! grep "$addDir" $BASH_RC > /dev/null 2>&1; then
echo "# @CPACK_PACKAGE_NAME@_DIR" >> $BASH_RC
echo ${addDir} >> $BASH_RC
printf "DIR of @CPACK_PACKAGE_NAME@ has been added in $BASH_RC\\n"
else
printf "DIR of @CPACK_PACKAGE_NAME@ already exists in $BASH_RC\\n"
fi
if [ ! "$(find $PREFIX/lib -name "*.a")" ]; then
addLDPath="export LD_LIBRARY_PATH=\"$PREFIX/lib:\$LD_LIBRARY_PATH\""
if ! grep "$addLDPath" $BASH_RC > /dev/null 2>&1; then
echo "# @CPACK_PACKAGE_NAME@ LD_LIBRARY_PATH" >> $BASH_RC
echo ${addLDPath} >> $BASH_RC
printf "LD_LIBRARY_PATH of @CPACK_PACKAGE_NAME@ has been added in $BASH_RC\\n"
else
printf "LD_LIBRARY_PATH of @CPACK_PACKAGE_NAME@ already exists in $BASH_RC\\n"
fi
fi
source $BASH_RC
printf "\\n"
fi
printf "Thank you for installing @CPACK_PACKAGE_NAME@!\\n"
exit 0
### Start of TAR.GZ file ###;
|