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版本的软件就没法用了,得重编,反之亦然,不方便同时看测试效果。

上网查了下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, 现在在项目根目录分别执行sconsscons LINUX_FB=true, 就能看到两个平台的输出分别被放到build_ubuntubuild_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编写成本挺低)

上面两个方法适用于三方库都在同一个文件夹的情况。