三方库组织
公司的项目初步三方库路径组织是这样,awtk-widget开头的是awtk的自定义控件,无源码的二进制库放在sourceless这个文件夹:
./3rd
├── awtk-widget-battery-widget
├── awtk-widget-border-text
├── awtk-widget-option-box
├── awtk-widget-range-rule-widget
├── awtk-widget-range-slider
├── awtk-widget-sonar-image
└── sourceless
sourceless文件夹下,分不同平台版本,平台文件夹下集合所有依赖库。每个依赖库我都按一个文件夹整理,include文件夹存放头文件,bin文件夹存放so库(为什么是bin不是lib?因为awtk内置的拷贝库脚本定死了只找bin文件夹下的库)。
project/3rd/sourceless/ubuntu$ tree -L 2
.
├── cairo
│ ├── bin
│ └── include
├── freetype
│ ├── bin
│ ├── include
│ └── share
├── libjpeg-turbo
│ ├── bin
│ └── include
├── libpng
│ ├── bin
│ ├── include
│ └── share
├── openssl
│ ├── bin
│ └── include
├── pixman
│ ├── bin
│ └── include
├── poco
│ ├── bin
│ └── include
├── version.md
└── zlib
├── bin
├── include
└── share
为什么这样放?估计当时觉得一个库就是一个package, 看着比较整洁。
这种堆放方式后面证明对项目维护十分麻烦,对于上面的cairo, poco, libjpeg, zlib等库,每一个包我都要在SConscript里面指明路径:
SOURCE_3RD_DEPS_PATH = os.path.normpath(os.path.join(os.getcwd(), '3rd/sourceless'))
if PLATFORM == 'Linux':
if LINUX_FB == True:
SOURCE_3RD_DEPS_PATH = (os.path.join(SOURCE_3RD_DEPS_PATH, 'T113'))
else:
SOURCE_3RD_DEPS_PATH = (os.path.join(SOURCE_3RD_DEPS_PATH, 'ubuntu'))
elif PLATFORM == 'Windows':
SOURCE_3RD_DEPS_PATH = os.path.join(SOURCE_3RD_DEPS_PATH, 'win')
DEPENDS_LIBS += [
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'cairo'),
'shared_libs' : ['cairo']
},
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'freetype'),
'shared_libs' : ['freetype']
},
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'libjpeg-turbo'),
'shared_libs' : ['jpeg', 'turbojpeg']
},
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'pixman'),
'shared_libs': ['pixman-1']
},
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'libpng'),
'shared_libs' : ['png16']
},
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'zlib'),
'shared_libs' : ['z']
},
{
'root': os.path.join(SOURCE_3RD_DEPS_PATH, 'openssl'),
'shared_libs': ['crypto', 'ssl']
},
{
'root' : os.path.join(SOURCE_3RD_DEPS_PATH, 'poco'),
'shared_libs' : ['PocoFoundation', 'PocoUtil', 'PocoXML', 'PocoJSON', 'PocoCrypto', 'PocoNet', 'PocoNetSSL']
}
]
...
helper.set_deps(DEPENDS_LIBS)
这样每加一个库我都要更新一次DEPEND_LIBS,几个库的小项目还好说,要是几十个库还得了。
awtk的helper.set_deps()函数可以自动把shared_libs指定的库列表拷贝过去,但一些情景也有失灵的时候,只能手动拷贝,这种结构一个个文件夹去拷贝肯定特别酸爽。
后面我参考linux内对头文件和so文件的组织方法,把头文件和库文件夹都扁平化了:
project/3rd/binary/ubuntu$ tree -L 2
.
├── bin
│ ├── addsymlinks.sh
│ ├── libcairo.so -> libcairo.so.2
│ ├── libcairo.so.2 -> libcairo.so.2.11600
│ ├── libcairo.so.2.11600 -> libcairo.so.2.11600.0
│ ├── libcairo.so.2.11600.0
│ ├── libcrypto.so -> libcrypto.so.1
│ ├── libcrypto.so.1 -> libcrypto.so.1.1
│ ├── libcrypto.so.1.1
│ ├── libfreetype.so -> libfreetype.so.6
│ ├── libfreetype.so.6 -> libfreetype.so.6.20
│ ├── libfreetype.so.6.20 -> libfreetype.so.6.20.0
│ ├── libfreetype.so.6.20.0
│ ├── libjpeg.so -> libjpeg.so.62
│ ├── libjpeg.so.62 -> libjpeg.so.62.4
│ ├── libjpeg.so.62.4 -> libjpeg.so.62.4.0
│ ├── libjpeg.so.62.4.0
....
├── include
│ ├── cairo
│ ├── cJSON.h
│ ├── evconfig-private.h
│ ├── event2
│ ├── freetype2
│ ├── jconfig.h
│ ├── jerror.h
│ ├── jmorecfg.h
│ ├── jpeglib.h
│ ├── libpng16
│ ├── mosquitto_broker.h
│ ├── mosquitto.h
....
└── VERSION.md
这样后面加三方库只需要CV include和bin就行了,不用修改SConstruct, 要手动拷贝直接cp -P xxx/bin/* *
就行。
THIRD_SRC = os.path.join(os.getcwd(), '3rd/')
BINARY_SRC = os.path.join(THIRD_SRC, 'binary')
if PLATFORM == 'Linux':
if LINUX_FB == True:
BINARY_SRC = (os.path.join(BINARY_SRC, 't113'))
else:
BINARY_SRC = (os.path.join(BINARY_SRC, 'ubuntu'))
BINARY_LIB_PATH = [
os.path.join(BINARY_SRC, 'bin')
]
BINARY_LIB_NAMES = ["mosquitto", "mosquittopp", "nanomsg"]
BINARY_LIB_NAMES += ["cairo", "freetype", "jpeg", "turbojpeg", "pixman-1", "png16", "z", "crypto", "ssl"]
BINARY_LIB_NAMES += ["PocoFoundation", "PocoUtil", "PocoXML", "PocoJSON", "PocoCrypto", "PocoNet", "PocoNetSSL"]
DEPENDS_LIBS += [
{
"root" : os.path.join(BINARY_LIB_PATH, ''),
'shared_libs': BINARY_LIB_NAMES,
'static_libs': []
}
]
项目连带自定义控件一起编译
需要加这么一行,不然scons只会编译项目本身:
app.prepare_depends_libs(ARGUMENTS, helper, DEPENDS_LIBS)
这个方法也适用于其他基于Sconstruct的三方库项目。
不同平台区分文件夹输出
awtk的scons编译默认会把执行文件输出到bin文件夹,而.o文件会和源代码混在一起。
这样涉及跨平台编译会有个问题,由于不管什么平台到默认输出到bin文件夹,而后一个平台的编译输出会覆盖前一个平台的编译输出,导致如果编了机器版本的软件,pc版本的软件就没法用了,得重编,反之亦然,不方便同时看测试效果。
上网查了下scons的SConscript有个variant_dir参数可以输出build文件夹位置,然后我就去看awtk对于scons的封装脚本,果然在app_helper_base.py看到这么一段:
def SConscript(self, SConscriptFiles):
if not self.BUILD_DIR:
Script.SConscript(SConscriptFiles)
else:
env = Environment.Environment()
env.Default(self.BUILD_DIR)
for sc in SConscriptFiles:
dir = os.path.dirname(sc)
build_dir = os.path.join(self.BUILD_DIR, dir)
Script.SConscript(sc, variant_dir=build_dir, duplicate=False)
其中self.BUILD_DIR是awtk可通过ARGUMENTS指定的环境参数。
在项目的SConstrct里可以这么加,我的情况是需要跨windows, ubuntu, 嵌入式linux机器三个平台:
PLATFORM = platform.system()
if PLATFORM == 'Linux':
if LINUX_FB == True:
ARGUMENTS['BUILD_DIR'] = 'build_linux_fb'
else:
ARGUMENTS['BUILD_DIR'] = 'build_ubuntu'
elif PLATFORM == 'Windows':
ARGUMENTS['BUILD_DIR'] = 'build_windows'
测试环境为ubuntu20.04, 现在在项目根目录分别执行scons
和scons LINUX_FB=true
, 就能看到两个平台的输出分别被放到build_ubuntu
和build_linux_fb
文件夹了。
├── build_linux_fb
│ ├── bin
│ ├── lib
│ ├── src
│ └── tests
├── build_ubuntu
│ ├── bin
│ ├── lib
│ ├── src
│ └── tests
不过也有副作用,如果加了三方库的话,对于之前用helper.set_deps(DEPENDS_LIBS)
, DEPEND_LIBS里指定的每个三方库文件夹三方库必须被放在BUILD_DIR下,也就是我现在对于之前的二进制库不得不改成这样:
project/3rd/binary$ tree -L 2
.
├── build_linux_fb
│ ├── bin
│ ├── include
│ └── VERSION.md
└── build_ubuntu
├── bin
├── include
└── VERSION.md
同时项目的scripts/release.py需要修改输出路径,默认是写死bin的。
OS_NAME = platform.system()
PRJ_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUTPUT_DIR = join_path(PRJ_DIR, 'release')
# old
# BIN_DIR = join_path(PRJ_DIR, 'bin')
# new
BIN_DIR = join_path(PRJ_DIR, 'build_linux_fb/bin')
三方库软链接问题
项目用到mosquitto和poco两个库,编译的时候发现bin/ld报找不到库:
/bin/ld: cannot find -lmosquitto
/bin/ld: cannot find -lmosquittopp
/bin/ld: cannot find -lnanomsg
/bin/ld: cannot find -lPocoFoundation
/bin/ld: cannot find -lPocoUtil
/bin/ld: cannot find -lPocoXML
/bin/ld: cannot find -lPocoJSON
/bin/ld: cannot find -lPocoCrypto
/bin/ld: cannot find -lPocoNet
/bin/ld: cannot find -lPocoNetSSL
工作由于赶工,一时半会找不到办法,只好把用到的库都拷贝到/usr/local/lib
去,但毕竟会污染环境,每次部署到一个新环境就得这么做一次,不是什么好方法。
有时间后开始探索,检查编译log,明明-L路径已经指明到有库的路径3rd/binary/build_ubuntu/bin
:
scons: Building targets ...
g++ -o build_ubuntu/bin/demo -Wl,-rpath=./bin -Wl,-rpath=./ -Wl,-rpath=/project/build_ubuntu/bin build_ubuntu/src/common/battery.o -L3rd/binary/build_ubuntu/bin -L3rd/binary/build_ubuntu/lib -lmosquitto -lmosquittopp -lnanomsg -lPocoFoundation -lPocoUtil -lPocoXML -lPocoJSON -lPocoCrypto -lPocoNet -lPocoNetSSL -lsetting_slider -lsonar_image -lrange_slider -lrange_rule_widget -loption_box -lborder_text -lbattery_widget -lmvvm -lawtk -lGL -lgtk-3 -lgdk-3 -lglib-2.0 -lgobject-2.0 -lXext -lX11 -lsndio -lstdc++ -lasound -lpthread -lm -ldl
百思不得上网搜索,查到了ld --verbose
可以输出链接库的查找过程,于是我试了下输出:
ld -lPocoFoundation --verbose
中间略去一堆....
==================================================
ld: mode elf_x86_64
attempt to open /usr/local/lib/x86_64-linux-gnu/libPocoFoundation.so failed
attempt to open /usr/local/lib/x86_64-linux-gnu/libPocoFoundation.a failed
attempt to open /lib/x86_64-linux-gnu/libPocoFoundation.so failed
attempt to open /lib/x86_64-linux-gnu/libPocoFoundation.a failed
attempt to open /usr/lib/x86_64-linux-gnu/libPocoFoundation.so failed
attempt to open /usr/lib/x86_64-linux-gnu/libPocoFoundation.a failed
attempt to open /usr/lib/x86_64-linux-gnu64/libPocoFoundation.so failed
attempt to open /usr/lib/x86_64-linux-gnu64/libPocoFoundation.a failed
attempt to open /usr/local/lib64/libPocoFoundation.so failed
attempt to open /usr/local/lib64/libPocoFoundation.a failed
attempt to open /lib64/libPocoFoundation.so failed
attempt to open /lib64/libPocoFoundation.a failed
attempt to open /usr/lib64/libPocoFoundation.so failed
attempt to open /usr/lib64/libPocoFoundation.a failed
attempt to open /usr/local/lib/libPocoFoundation.so failed
attempt to open /usr/local/lib/libPocoFoundation.a failed
attempt to open /lib/libPocoFoundation.so failed
attempt to open /lib/libPocoFoundation.a failed
attempt to open /usr/lib/libPocoFoundation.so failed
attempt to open /usr/lib/libPocoFoundation.a failed
attempt to open /usr/x86_64-linux-gnu/lib64/libPocoFoundation.so failed
attempt to open /usr/x86_64-linux-gnu/lib64/libPocoFoundation.a failed
attempt to open /usr/x86_64-linux-gnu/lib/libPocoFoundation.so failed
attempt to open /usr/x86_64-linux-gnu/lib/libPocoFoundation.a failed
原来定死了是从无后缀的.so开始找的,无后缀so通常都是指向带后缀版本so库的软链接,而我的lib路径下都是带版本后缀的,反而没有软链接,估计是当时源码编译后直接CV手动拷贝,于是那些软链接就丢失了,后面才查到应该用cp -P
去拷贝。
一个个ln -s
去给这些so库建链接肯定不是什么好办法,遂在网上寻找可以自动建链接的脚本,果然找到一个, 自己学习过程加工了下,如下:
#!/bin/bash
# liblinks - generate symbolic links
# given libx.so.0.0.0 this would generate links for libx.so.0.0, libx.so.0, libx.so
#
# ref: https://stackoverflow.com/questions/462100/bash-script-to-create-symbolic-links-to-shared-libraries
LIBFILES=`ls lib*.so.*`
for FILE in $LIBFILES;
do
shortlib=$FILE
basename=$FILE
echo "=================In loop========================"
while extn=$(echo $shortlib | sed -n '/\.[0-9][0-9]*$/s/.*\(\.[0-9][0-9]*\)$/\1/p')
echo "basename: $basename extn:$extn"
[ -n "$extn" ]
do
shortlib=$(basename $shortlib $extn)
echo "ln -fs $basename $shortlib"
ln -fs $basename $shortlib
basename=$shortlib
done
echo "=====================Out loop=================="
done
将脚本命名为addsymlink.sh
,放到三方库bin文件夹中,执行,然后回到项目根目录scons编译,就没再报bin/ld
的问题了,看来就是无软链接所致,当前在windows上只知道dll不知道lib的教训在linux上又演了一把。
不过这个方法并没有完美解决所有问题,有些库十分顽固,必须提供带版本号的版本,否则就无法运行:
kp25s_expo$ ./bin/demo
./bin/demo: error while loading shared libraries: libjpeg.so.62: cannot open shared object file: No such file or directory
Poco库同样带版本号就没有这种现象,ldd一查,发现Poco库已经自动链接到我存放的库路径了,libjpeg库就没有,不知道是我设了什么环境变量导致还是库的特性。
ldd ./bin/demo
linux-vdso.so.1 (0x00007fff637d0000)
libPocoFoundation.so.64 => /home/zhangdalin/AWStudioProjects/project/3rd/sourceless/ubuntu/poco/bin/libPocoFoundation.so.64 (0x00007f15a20d5000)
libPocoNet.so.64 => /home/zhangdalin/AWStudioProjects/project/3rd/sourceless/ubuntu/poco/bin/libPocoNet.so.64 (0x00007f15a1f94000)
libPocoNetSSL.so.64 => /home/zhangdalin/AWStudioProjects/project/3rd/sourceless/ubuntu/poco/bin/libPocoNetSSL.so.64 (0x00007f15a1f47000)
libjpeg.so.62 => not found
目前也想不到很好的解决方法,zlib和libjpeg都有这种问题,还好出问题的库就一两个,在机器打包后可以直接ln -s libxx.so libxx.so.x
给这些库建软链接解决问题,要是大量库都是这种情况只能cp -P
大法了。
(2025.1.24 ADD 上述问题已经得到解决,见:https://github.com/zlgopen/awtk/issues/895)
找到两个解决方法: 1.自己写拷贝函数,指定文件夹把库拷过去:
import os
import shutil
import subprocess
import sys
import glob
import platform
def copy_binary_libs_to_build_dir(dst_path, lib_list, src_path):
"""
将 dst_path 中匹配 lib_list 的文件拷贝到 src_path 文件夹。
对于 Linux 系统,会查找 lib_list 中对应元素的 .so 文件是否存在,
如果不存在就调用 dst_path 中的 addsymlink.sh 更新软链接。
Linux 系统情况下,将拷贝对应元素所有的匹配 .so* 的文件(包括软链接和带版本号后缀的 so 文件)。
"""
PLATFORM = platform.system()
# 确保目标目录存在
os.makedirs(src_path, exist_ok=True)
for lib_name in lib_list:
# 根据平台确定库文件的后缀
if PLATFORM == 'Windows':
lib_file = f'{lib_name}.dll'
src_lib_path = os.path.join(dst_path, lib_file)
dest_lib_path = os.path.join(src_path, lib_file)
# 检查库文件是否存在
if not os.path.exists(src_lib_path):
print(f"Warning: Library {lib_file} not found in {dst_path}.")
continue
# 拷贝文件
shutil.copy2(src_lib_path, dest_lib_path)
print(f"Copied {src_lib_path} to {dest_lib_path}.")
elif PLATFORM == 'Linux':
# Linux 系统下,查找 lib_name 对应的 .so 文件
lib_file_pattern = f'lib{lib_name}.so*'
src_lib_pattern = os.path.join(dst_path, lib_file_pattern)
matching_files = glob.glob(src_lib_pattern)
if not matching_files:
# 如果找不到 .so 文件,尝试调用 addsymlink.sh 更新软链接
addsymlink_script = os.path.join(dst_path, 'addsymlink.sh')
if os.path.exists(addsymlink_script):
print(f"Library {lib_file_pattern} not found. Running addsymlink.sh to update symlinks...")
try:
subprocess.check_call([addsymlink_script], cwd=dst_path)
except subprocess.CalledProcessError as e:
print(f"Error: Failed to run addsymlink.sh: {e}")
sys.exit(1)
# 再次查找 .so 文件
matching_files = glob.glob(src_lib_pattern)
if not matching_files:
print(f"Error: Library {lib_file_pattern} still not found after running addsymlink.sh.")
sys.exit(1)
else:
print(f"Error: Library {lib_file_pattern} not found and addsymlink.sh does not exist.")
sys.exit(1)
# 拷贝所有匹配的 .so* 文件
for src_lib_path in matching_files:
dest_lib_path = os.path.join(src_path, os.path.basename(src_lib_path))
shutil.copy2(src_lib_path, dest_lib_path)
print(f"Copied {src_lib_path} to {dest_lib_path}")
else:
print(f"Unsupported platform: {PLATFORM}")
sys.exit(1)
2.看了下prepare_depends_libs函数有这么一段,可以指定把对应文件夹给拷贝到BUILD_DIR下:
if 'needed_files' in lib:
if Script.GetOption('clean'):
clear_needed_files(helper, lib['root'], lib['needed_files'])
else:
copy_needed_files(helper, lib['root'], lib['needed_files'])
那么SConstruct可以改成这样:
DEPENDS_LIBS += [
{
'root': BINARY_LIB_PATHS,
'shared_libs':[],
'needed_files': ['bin']
}
]
app.prepare_depends_libs(ARGUMENTS, helper, DEPENDS_LIBS)
效果一致,后者好处是不用写新函数(虽然这种逻辑AI编写成本挺低)
上面两个方法适用于三方库都在同一个文件夹的情况。