Linux 下将程序打包为安装包

对于 Linux 下的程序打包,个人首推 FPM 构建工具。它比 dpkg、rpmbuild 要好用的多(至少 2021 年那会如此),并且它同时支持 deb、rpm 等包格式。

以下回顾一下以前写过的安装包构建脚本:

#!/bin/bash

#-----------------------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------- ---------------------------------------------------- 该块属于定制化部分,根据项目需要进行修改

# 软件名称,主要作用于安装包名称和软件包管理器显示的软件名称
name="AppName"
# 软件中文名称,主要作用于桌面快捷方式
name_zh="你的软件名称"

# 软件描述
comment="Description"
comment_zh="你的软件描述"

# 版本号:
# 版本号通常由开发人员指定,其由 4 个数值组成,分别为:主版本号.子版本号.修正版本号.编译版本号。
#
# 主版本号:当产品有大的功能性变动,不再向前兼容时,比如产品重构,重大功能修改等,可变更主版本号。
# 主版本号提升后,子版本号与修正版本号归 0,编译版本号归 1。
#
# 子版本号:新功能增加、重要的且大批量的 Bug 修复或稳定版本发布时,将变更子版本号以体现每次发布。
# 子版本号提升后,修正版本号归 0,编译版本号归 1。
#
# 修正版本号:主要用于表述该产品的子版本号在发布后,经过了多少次修正;当完成某个或批次的 bug 修正、功能小改动并要提测时,可变更修正版本号。
# 修正版本号提升后,编译版本号归 1。
#
# 编译版本号:当对提测版本进行修正时将用到编译版本号,用于区分提测版本,避免每次提交给测试人员的程序中出现相同的版本号。
# 编译版本号一般由编译器自动累加,当上级版本号变更时由开发人员手动重置为 1。
version="1.1.0"
# 编译版本号
iteration=1

# 目标平台的 CPU 架构
arch="arm64"

# 安装包类型
type="deb"


# 程序的安装目录
installPath="opt/AppDir"

# 程序的可执行文件名称,其通常与软件名称保持一致
exe="${name}"
# 程序启动脚本名称,其通常与程序的可执行文件名称保持一致
startupScript="${exe}.sh"


# 待打包的程序文件所在目录
source="file"

# 待打包程序的资源文件所在目录
resourcesPath="resources"


# desktop 文件名称,我们可以将其简单理解为桌面快捷方式文件。该文件描述了程序的启动配置信息。
desktopFile="${name}.desktop"

# 桌面图标文件
desktopIcon="icons/desktop.png"

# 安装包名称
package=${name}_${version}-${iteration}_${arch}.${type}

# 安装包的输出目录
outputDir="packages"

# 临时文件目录
temp="temp"

# 必要的程序子目录,由开发人员维护
installPath_bin="${installPath}/bin"
installPath_desktop="${installPath}/desktop"
installPath_resources="${installPath}/resources"
installPath_script="${installPath}/script"

# 一些必须的安装脚本文件,由开发人员维护
# 这些脚本将由软件包管理器自动调用,详情参见 fpm --help 的输出
afterInstall="after-install.sh"
afterUpgrade="after-upgrade.sh"
beforeRemove="before-remove.sh"
beforeUpgrade="before-upgrade.sh"


# 指定 qt 将要加载的 QPA 插件,通常我们都指定 xcb。
# NOTE: 
# 如果我们不指定的话将有可能出现程序无法启动的问题(例如 UOS+Huawei 系统)。
# 详情请参见:https://blog.martin-graesslin.com/blog/2018/03/unsetting-qt_qpa_platform-environment-variable-by-default/
defineQpa="QT_QPA_PLATFORM='xcb'"


#-----------------------------------------------------------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------------------------------------------------------



# 当任何语句出现错误时中断执行
set -e

curData=$(date "+%Y-%-m-%-d-%H%M%S")


# -------- 生成一些必要的程序安装文件 start -------- #

# 删除上次打包时可能遗留的程序安装目录
rm -rf ./${installPath}
# 生成必要的安装目录
mkdir -p ./${installPath}
mkdir -p ./${installPath_bin}
mkdir -p ./${installPath_desktop}
mkdir -p ./${installPath_resources}
mkdir -p ./${installPath_script}


echo "移动源文件至构建目录下..."
# 将待打包的程序文件移至程序安装目录下(为了效率我们选择移动文件而不是复制)
mv ${source}/* ./${installPath_bin}
rm -rf ${source}


echo "复制资源文件至构建目录下..."
# 放置相关资源文件至程序的资源目录下
cp -rp ${resourcesPath}/* ./${installPath_resources}/


echo "生成 ${startupScript} 脚本..."
# 生成启动脚本,该脚本用于启动程序
cat > ./${installPath_script}/${startupScript} << EOF
#!/bin/bash

${defineQpa}

# 为保险起见,每次都重新赋值一下权限
chmod -R 777 /${installPath}
cd /${installPath_bin}
./${exe}
EOF

echo "生成 ${afterInstall} 脚本..."
# 生成 afterInstall 脚本,该脚本将在程序安装后由软件包管理器自动调用
cat > ./${installPath_script}/${afterInstall} << EOF
#!/bin/bash

chmod -R 777 /${installPath}

# 生成桌面快捷方式 #
# 我们只创建链接文件,以图方便后续 desktop 的更新

deskFileName=${desktopFile}
desktopFile=/${installPath_desktop}/\${deskFileName}

if [ -e "\${desktopFile}" ]; then
	zhDesktop=/root/桌面
	enDesktop=/root/Desktop

	if [ -d "\${zhDesktop}" ]; then
		ln -sf \${desktopFile} \${zhDesktop}/
		chmod 777 \${zhDesktop}/\${deskFileName}
	fi
	if [ -d "\${enDesktop}" ]; then
		ln -sf \${desktopFile} \${enDesktop}/
		chmod 777 \${enDesktop}/\${deskFileName}
	fi

	for FILENAME in /home/*; do
		zhDesktop=\${FILENAME}/桌面
		enDesktop=\${FILENAME}/Desktop
		if [ -d "\${zhDesktop}" ]; then			
			ln -sf \${desktopFile} \${zhDesktop}/
			chmod 777 \${zhDesktop}/\${deskFileName}
		fi
		if [ -d "\${enDesktop}" ]; then			
			ln -sf \${desktopFile} \${enDesktop}/
			chmod 777 \${enDesktop}/\${deskFileName}
		fi
	done
fi

applications=/usr/share/applications
ln -sf \${desktopFile} \${applications}/
chmod 777 \${applications}/\${deskFileName}
EOF

echo "生成 ${afterUpgrade} 脚本..."
# 生成 afterUpgrade 脚本,该脚本将在程序升级后由软件包管理器自动调用
cat > ./${installPath_script}/${afterUpgrade} << EOF
#!/bin/bash

chmod -R 777 /${installPath}
EOF

echo "生成 ${beforeRemove} 脚本..."
# 生成 beforeRemove 脚本,该脚本将在程序卸载前由软件包管理器自动调用
cat > ./${installPath_script}/${beforeRemove} << EOF
#!/bin/bash

# 删除桌面快捷方式 #

deskFileName=${desktopFile}
zhDesktop=/root/桌面/\${deskFileName}
enDesktop=/root/Desktop/\${deskFileName}
applications=/usr/share/applications/\${deskFileName}

if [ -e "\${zhDesktop}" ]; then
	rm -f \${zhDesktop}
fi
if [ -e "\${enDesktop}" ]; then
	rm -f \${enDesktop}
fi
if [ -e "\${applications}" ]; then
	rm -f \${applications}
fi

for FILENAME in /home/*; do
	zhDesktop=\${FILENAME}/桌面/\${deskFileName}
	enDesktop=\${FILENAME}/Desktop/\${deskFileName}
	if [ -e "\${zhDesktop}" ]; then
		rm -f \${zhDesktop}
	fi
	if [ -e "\${enDesktop}" ]; then
		rm -f \${enDesktop}
	fi
done
EOF


echo "生成 ${desktopFile} ..."
# 生成 desktop 文件
cat > ./${installPath_desktop}/${desktopFile} << EOF
#!/usr/bin/env xdg-open
[Desktop Entry]
Name=${name}
Name[zh_CN]=${name_zh}
Comment=${comment}
Comment[zh_CN]=${comment_zh}
GenericName=${name}
GenericName[zh_CN]=${name_zh}
Exec=/${installPath_script}/${startupScript} %u
Icon=/${installPath_resources}/${desktopIcon}
Terminal=false
Type=Application
X-Ubuntu-Touch=true
Categories=Office;
StartupNotify=false
EOF


# 为了支持多用户使用,我们直接为程序安装目录赋上 777 权限
chmod 777 -R ./${installPath}

# -------- 生成一些必要的程序安装文件 end -------- #



# -------- 生成一些必要的临时文件 start -------- #

# 删除上次打包时可能遗留的临时目录
rm -rf ./${temp}
mkdir -p ./${temp}

createTempScript() 
{
	fileName=$1
cat > ./${temp}/${fileName} << EOF
#!/bin/bash

echo "%{version}-%{release}: exec ${fileName} for arg=\$*"

scriptPath=/${installPath_script}
scriptFile=\${scriptPath}/${fileName}

if [ -e "\${scriptFile}" ]; then
	cd \${scriptPath}
	bash \${scriptFile} \$@
else
	echo \${scriptFile} not exist
fi
EOF
}

# XXX: 
# 生成打包时引用的安装脚本,这些脚本实际上什么也不做,只是转调程序安装目录下的脚本。
# 这么做是为了让程序能够在安装后对这些脚本进行修改。虽然几乎用不上该特性。
createTempScript ${afterInstall}
createTempScript ${afterUpgrade}
createTempScript ${beforeRemove}
createTempScript ${beforeUpgrade}

# -------- 生成一些必要的临时文件 end -------- #




mkdir -p ./${outputDir}

echo "开始构建安装包..."
# 执行 fpm 打包
fpm -f -s dir -t ${type} -n ${name} -v ${version} --iteration ${iteration} -a ${arch} --after-install ./${temp}/${afterInstall} --before-remove ./${temp}/${beforeRemove} --before-upgrade ./${temp}/${beforeUpgrade} --after-upgrade ./${temp}/${afterUpgrade} --deb-no-default-config-files --log warn --no-auto-depends -p ./${outputDir}/${package} -C . ${installPath}

echo "成功构建安装包至 ./${outputDir}/${package}"


echo "清理临时文件和构建目录..."
rm -rf ./${installPath}
rm -rf ./${temp}



echo "完成"
posted @ 2025-10-21 22:59  邓加领  阅读(31)  评论(0)    收藏  举报