AWTK項目編譯問題整理(1)
三方庫組織
公司的項目初步三方庫路徑組織是這樣,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版本的軟件就沒法用了,得重編,反之亦然,不方便同時看測試效果。
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編寫成本挺低)

浙公網安備 33010602011771號