• 首页
  • 加入
  • RSS
  • Friday, September 16, 2022

    这是一个模板文章。各个 SIG 在实际使用此博客模板时可根据实际需求对此模板进行任何调整。

    我们要求 deepin 社区中的小组每季度至少能有一到两次对公众公开的小组成果展示,用以分享成果,使得社区大众均能受益。通过使用此博客模板,SIG 即可直接在自己的小组博客站点中提供小组成果的相应博客介绍,而 SIG 委员会以及其它感兴趣的小组即可直接利用 RSS 订阅来获得相应的内容更新。

    对于成果公示类的文章,您即可通过“成果公示”或类似的标签来标记文章,这样,在此标签内的文档就会被生成到同一标签类别的 RSS 之中,以供订阅了。

    这是一个模板文章。各个 SIG 在实际使用此博客模板时可根据实际需求对此模板进行任何调整。

    我们要求 deepin 社区中的小组每季度至少能有一到两次对公众公开的小组成果展示,用以分享成果,使得社区大众均能受益。通过使用此博客模板,SIG 即可直接在自己的小组博客站点中提供小组成果的相应博客介绍,而 SIG 委员会以及其它感兴趣的小组即可直接利用 RSS 订阅来获得相应的内容更新。

    对于成果公示类的文章,您即可通过“成果公示”或类似的标签来标记文章,这样,在此标签内的文档就会被生成到同一标签类别的 RSS 之中,以供订阅了。

    这是一个模板文章。各个 SIG 在实际使用此博客模板时可根据实际需求对此模板进行任何调整。

    我们要求 deepin 社区中的小组每季度至少能有一到两次对公众公开的小组成果展示,用以分享成果,使得社区大众均能受益。通过使用此博客模板,SIG 即可直接在自己的小组博客站点中提供小组成果的相应博客介绍,而 SIG 委员会以及其它感兴趣的小组即可直接利用 RSS 订阅来获得相应的内容更新。

    对于成果公示类的文章,您即可通过“成果公示”或类似的标签来标记文章,这样,在此标签内的文档就会被生成到同一标签类别的 RSS 之中,以供订阅了。

    生成 Config.cmake 文件

    生成 Config.cmake 文件路径要求与 pkg-config 一致

    应该提供 FooConfig.cmake.in 使用 FULL 版本的 GNUInstallDirs 变量替换路径

    相关修改:

    使用 find_dependency 代替 find_package

    cmake 官方提供了 CMakeFindDependencyMacro 模块,专门用在 Config.cmake 文件中,find_dependency 和 find_package 用法完全相同,因此可以简单的把原来的 find_package 替换成 find_dependency。

    find_dependency 的优点是如果 A 的 Config.cmake 寻找 B 的,如果失败 find_package 只会提示 B 的错误,而 find_dependency 还在报错中输出调用链,清晰显示是谁在找 B。

    find_dependency 应该在 set 路径之后。

    与 pkg-config 的 Requires 类似,这里只 find 传播的构建依赖。

    使用 configure_package_config_file 代替 configure_file

    使用 FULL 版本的 GNUInstallDirs 变量虽然正确,但还有一个缺陷,生成的是绝对路径,库必须安装到对应目录才可以。而 configure_package_config_file 可以自动计算相对的路径,适用性更广泛。

    configure_package_config_file(misc/PkgNameConfig.cmake.in
        ${CMAKE_CURRENT_BINARY_DIR}/PkgNameConfig.cmake
        INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/PkgName"
        PATH_VARS INCLUDE_INSTALL_DIR TOOL_INSTALL_DIR
    )
    

    INSTALL_DESTINATION 是 Config.cmake 的安装路径,但这里并不会安装,只是用来计算相对路径的,所以 install 还是需要写的。

    PATH_VARS 传入 PkgNameConfig.cmake.in 里会用到的路径变量。

    为了使用 configure_package_config_file,Config.cmake.in 也需要做出以下变化:

    • 在开头写一行 @PACKAGE_INIT@,这会展开生成 CMakePackageConfigHelpers 提供的宏,包括set_and_check,check_required_components。

    • 使用 set_and_check 替代 set 设置路径,可以在引入时检查对应的路径文件是否存在。(注意如果拆包了其他包的文件就不要 check 了)。

    • 路径变量前面加 PACKAGE,如 @INCLUDE_INSTALL_DIR@ 改成 @PACKAGE_INCLUDE_INSTALL_DIR@,需要上面 configure_package_config_file 的 PATH_VARS 参数传进来。

    • 文件后面使用 check_required_components(PkgName) 检查组件,即使没有拆分组件也应该检查,明确告诉 cmake 该包没有组件。

    使用 Targets

    对于新增的库,建议使用新式 cmake 写法,使用 Targets,而非手动设置路径:

    建议参考: Installing a Config.cmake file

    使用 find_package 引入库

    find_package 有两个模式,Modules Mode 和 Config Mode,在 Modules Mode下,find_package(PkgName) 会寻找命名为 FindPkgName.cmake 的模块,由此模块负责寻找,这种模块一般是由 cmake 官方提供或者调用者自己编写。Config Mode 就是寻找 PkgNameConfig.cmake 模块了,由库自己提供。

    find_package(DtkCore)
    message("${DtkCore_VERSION}") # 6.2 以前没有提供
    message("${DtkCore_LIBRARIES}")
    message("${DtkCore_LIBRARY_DIRS}") # !没有提供
    message("${DtkCore_INCLUDE_DIRS}") # !没有提供
    
    find_package(Dtk REQUIRED Core Gui Widget)
    # 与 pkg_check_modules 不同,后面是同一(组)软件提供的不同 components,等价于:
    find_package(Dtk)
    find_package(DtkCore)
    find_package(DtkGui)
    find_package(DtkWidget)
    

    find_package 提供的变量请检查 Config.cmake 提供了什么,不像 pkg_check_modules 那样命名统一,在 6.2 以前,提供 DTKCORE_INCLUDE_DIR, 6.2 之后使用 DtkCore_INCLUDE_DIR(旧版变量仍保留)。

    使用 find_package 引入 dtk 的软件,如果用了 DtkCore_INCLUDE_DIRS 请修改,这是 pkg_check_modules 引入才有的变量,另外 DTKCORE_INCLUDE_DIR 升级后 建议换成 DtkCore_INCLUDE_DIR。Gui,Widget 同理。

    参考资料:

    编写 FindXXX.cmake 模块

    对于没有提供 pkg-config 也没有提供 Config.cmake 并且 cmake 官方没有提供对应模块的,建议自行写一个 FindXXX.cmake 模块

    1. 通过 find_path,find_library 函数计算出 foo_INCLUDE_PATH, foo_LIBRARY。

    2. 通过 FindPackageHandleStandardArgs 模块提供的 find_package_handle_standard_args 根据 foo_INCLUDE_PATH, foo_LIBRARY 是否有值设置 foo_FOUND,这样是让 find_package 识别此模块所需要的。

    3. 一般会把 foo_INCLUDE_PATH, foo_LIBRARY 标记为高级(mark_as_advanced),不再 cmake gui 显示。

    编写模块自由度很高,还可以根据需求检查 check_function_exists,check_symbol_exists 影响 foo_FOUND 而且只需要编写一次,其他项目可以直接复用。

    修改示例:

    dtkcommon 相关

    使用 dconfig_override_files 请不要忘记通过 find_package(Dtk) 引入 dtkcommon 提供的模块,而不是靠 DtkWidget 的传播依赖(实际上不应该传播 dtkcommon 的)。

    使用 pkg-config

    pkg-config 是一个在源代码编译时查询已安装的库的使用接口的计算机工具软件。pkg-config原本是设计用于Linux的,但现在在各个版本的BSD、windows、Mac OS X和Solaris上都有着可用的版本。

    它输出已安装的库的相关信息,包括:

    • C/C++编译器需要的输入参数
    • 链接器需要的输入参数
    • 已安装软件包的版本信息

    使用 cmake 生成 .pc 文件

    一个比较规范的 foo.pc.in 如下:

    prefix=@CMAKE_INSTALL_PREFIX@
    exec_prefix=${prefix}
    libdir=@CMAKE_INSTALL_FULL_LIBDIR@
    includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@/libimageviewer
    
    Name: foo
    Description: foo is a demo lib
    Version: @PROJECT_VERSION@
    Libs: -L${libdir} -lfoo
    Cflags: -I${includedir}
    Requires: Qt5Core Qt5Gui Qt5Widgets 
    

    这里的 Name 是显示的名称,文件名才是搜索用,显示名一般不要全部大写。

    Requires:设置 foo 的依赖, 这里有2个条件:

    1. 必须是传播的构建依赖,即某软件构建依赖 foo,构建环境必须有 bar 才可以编译
    2. 必须是提供 .pc 文件的依赖

    另外不要忘记加 -L 参数(链接库地址),-I 参数(头文件地址)。

    相关修改: https://github.com/linuxdeepin/image-editor/pull/27

    其中路径问题(libdir/includedir)这里再强调一下, 不规范做法:

    prefix=/usr
    exec_prefix=${prefix}
    libdir=${prefix}/lib
    includedir=${prefix}/include/foo
    

    这种是硬编码路径,可移植性非常差。

    prefix=@CMAKE_INSTALL_PREFIX@
    exec_prefix=${prefix}
    libdir=${prefix}/lib
    includedir=${prefix}/include/foo
    

    相比上种方法,prefix 使用 cmake 变量,可以应对 prefix 不是 /usr 的情况了。

    prefix=@CMAKE_INSTALL_PREFIX@
    exec_prefix=${prefix}
    libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
    includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/foo
    

    相比上种方法,lib/include 目录使用 GNUInstallDirs 变量代替,灵活度更高。

    但是,这种并非最好的,因为(1)GNUInstallDirs 有特殊的拼接规则,如 PREFIX=/,LIBDIR=lib,应该拼接成 /usr/include 而非 /include。(2)CMAKE_INSTALL_LIBDIR 虽然默认是相对路径,但允许被直接设置成绝对路径。

    写在 .pc.in 只能简单拼接,无法判断特殊情况,最好由 cmake 进行拼接。GNUInstallDirs 提供 FULL 版本变量(本文第一个示例)就是智能拼接出来的,建议使用。当然,基于此自行设置变量也可以: set (INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_FULL_INCLUDEDIR}/libdtk-${CMAKE_PROJECT_VERSION}/DCore")

    使用 qmake 生成 pkgcconfig 文件

    CONFIG += create_prl create_pc
    # 生成 .pc 文件需要 .prl 中间文件,如果不需要可以使用 no_install_prl
    
    QMAKE_PKGCONFIG_NAME = foo
    QMAKE_PKGCONFIG_DESCRIPTION = foo is demo
    QMAKE_PKGCONFIG_INCDIR = $${HEADERDIR}
    QMAKE_PKGCONFIG_LIBDIR = $${LIBDIR}
    QMAKE_PKGCONFIG_DESTDIR = pkgconfig
    

    其中的 HEADERDIR,LIBDIR 是需要在 .pro 文件中设置的安装路径。

    cmake 使用 pkg-config

    不要混淆 find_package 和 pkg_check_modules

    cmake 导入

    find_package(PkgConfig REQUIRED) # 导入此模块才可以使用 pkg_check_modules
    pkg_check_modules(DtkCore REQUIRED dtkcore)
    message("${DtkCore_VERSION}")
    message("${DtkCore_LIBRARIES}") # 对应 -l 参数
    message("${DtkCore_LIBRARY_DIRS}") # 对应 -L 参数
    message("${DtkCore_INCLUDE_DIRS}") # 对应 -I 参数
    
    pkg_check_modules(Dtk REQUIRED dtkcore dtkgui dtkwidget)
    message("${Dtk_LIBRARIES}")
    message("${Dtk_LIBRARY_DIRS}")
    message("${Dtk_INCLUDE_DIRS}")
    

    参考资料

    qmake 使用 pkg-config

    CONFIG += link_pkgconfig
    PKGCONFIG += foo
    

    CMake 中有许多关于编译器和链接器的设置。当你需要添加一些特殊的需求,你应该首先检查 CMake 是否支持这个需求,如果支持的话,你就可以不用关心编译器的版本,一切交给 CMake 来做即可。 更棒的是,你可以在 CMakeLists.txt 表明你的意图,而不是通过开启一系列标志 (flag) 。

    优化参数(-O0/-O1/-O2/-O3/-Ofast/-Os) 和 debug 参数 (-g)

    CMAKE_BUILD_TYPE 包括 Debug, Release, RelWithDebInfoMinSizeRe 4 个选项

    它们对应参数是(不同编译平台可能有少许区别):

    1. Release: -O3 -DNDEBUG
    2. Debug: -O0 -g
    3. RelWithDebInfo: -O2 -g -DNDEBUG
    4. MinSizeRel: -Os -DNDEBUG

    这些参数可以通过 CMAKE_CXX_FLAGS_DEBUG,CMAKE_CXX_FLAGS_RELEASE… 查看,并不直接体现在 CMAKE_CXX_FLAGS 中。

    CMAKE_BUILD_TYPE 的默认值不是Release,也不属于上面4种,而是空值,即不附加任何参数。

    全局(或非debug模式)设置 -O3:

    建议改为将 CMAKE_BUILD_TYPE 设置默认 Release

    Release 模式加 O3, Debug 模式加 -g 等等

    不用加,这些都是无用功

    Release 模式加 -g

    如果软件发行版本需要 debug 符号,请用 RelWithDebInfo 模式

    Debug 加 -O3

    高级别的优化会严重影响 debug 调试, 可以改用 -Og,它允许不影响调试的优化

    同时有 -O1 -O3

    -O0/-O1/-O2/-O3/-Ofast 开启的优化参数前依次递增,是完全的子集关系,不是多多益善的。 -Os 是在 -O2 的基础上,尽可能优化程序大小。

    Release 模式使用 -Ofast 或其他优化级别。

    最好是 string(REPLACE "-O3" "-Ofast" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}),这样就没有原来的-O3了 如果是是降低优化基本,则必须这样替换。

    参考资料:

    de

    参考修改:

    c++标准版本 (比如 -std=c++11)

    直接使用 set(CMAKE_CXX_STANDARD 11)

    部分项目设置了 CMAKE_CXX_STANDARD 为 14/17, 又加了参数 -std=c++11,这是冲突的,请检查。 比如: https://github.com/linuxdeepin/deepin-movie-reborn/blob/0a08e29e78c4f35f787b999d857f696f804f8641/CMakeLists.txt#L18

    Hardening

    DEB_BUILD_HARDENING_FORMAT (gcc/g++ -Wformat -Wformat-security -Werror=format-security) DEB_BUILD_HARDENING_FORTIFY (gcc/g++ -D_FORTIFY_SOURCE=2) DEB_BUILD_HARDENING_STACKPROTECTOR (gcc/g++ -fstack-protector-strong) DEB_BUILD_HARDENING_PIE (gcc/g++ -fPIE -pie) DEB_BUILD_HARDENING_RELRO (ld -z relro) DEB_BUILD_HARDENING_BINDNOW (ld -z now)

    as-need 参数

    已知 as-need 参数 break 了 mold 连接器,可用 as-needed 代替。 相关:https://github.com/linuxdeepin/developer-center/issues/3345

    dl 库

    如果你需要链接到 dl 库,在 Linux 上可以使用 -ldl 标志,不过在 CMake 中只需要在 target_link_libraries 命令中使用内置的 CMake 变量 ${CMAKE_DL_LIBS} 。这里不需要模组或者使用 find_package 来寻找它。(这个命令包含了调用 dlopendlclose 的一切依赖)

    程序间优化(Interprocedural optimization)

    INTERPROCEDURAL_OPTIMIZATION,最有名的是 链接时间优化 以及 -flto 标志,这在最新的 CMake 版本中可用。你可以通过变量 CMAKE_INTERPROCEDURAL_OPTIMIZATION( CMake 3.9+ 可用)或对目标指定 INTERPROCEDURAL_OPTIMIZATION 属性来打开它。在 CMake 3.8 中添加了对 GCC 及 Clang 的支持。如果你设置了 cmake_minimum_required(VERSION 3.9) 或者更高的版本(参考 CMP0069),当在编译器不支持 INTERPROCEDURAL_OPTIMIZATION 时,通过变量或属性启用该优化会产生报错。你可以使用内置模块 CheckIPOSupported 中的 check_ipo_supported() 来检查编译器是否支持 IPO 。下面是基于 CMake 3.9 的一个例子:

    include(CheckIPOSupported)
    check_ipo_supported(RESULT result)
    if(result)
      set_target_properties(foo PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
    endif()
    

    位置无关的代码

    在 g++ 中,对编译生成动态库是 -fPIC/-fpic,对生成可执行文件是 -fPIE/-fpie。

    如果要把静态库链接到动态库,这一选项是必须的。其他情况也建议开启,可以防范一些内存攻击。

    在 cmake 中,对于动态库(SHARED and MODULE library),这一选项是默认开启的。对于静态库,在 cmake 3.14 版本之后(CMP0083 新标准),默认值是 POSITION_INDEPENDENT_CODE,而该值默认为假。

    可以通过 set(POSITION_INDEPENDENT_CODE True) 让静态库默认增加 “-fpic” 编译。

    最好通过 set_property 设置此选项:set_property(TARGET foo PROPERTY POSITION_INDEPENDENT_CODE TRUE),foo 为对应静态库或者可执行程序。

    此外,推荐使用 check_pie_supported(),检查一下编译器是否支持。

    参考资料:

    -lpthread

    POSIX thread 是基于 C/C++ 的标准线程库。在 cmake 3.1 以上的版本提供了 FindThreads 模块, 如果设置 THREADS_PREFER_PTHREAD_FLAG 变量,会优先使用 pthread。

    set(THREADS_PREFER_PTHREAD_FLAG ON)
    find_package(Threads REQUIRED)
    target_link_libraries(my_app PRIVATE Threads::Threads)
    

    参考文档:

    https://modern-cmake-cn.github.io/Modern-CMake-zh_CN/chapters/features/small.html

    设置时不要覆盖之前的选项

    错误示例:set(CMAKE_CXX_FLAGS “-Wall”) 正确示例:set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -Wall”)

    如果确实有覆盖的要求,请在源码注释说明,因为一般出现覆盖 flag 很可能是失误。

    判断架构

    遗留代码有使用 dpkg-architecture 判断的,改用 CMAKE_SYSTEM_PROCESSOR。

    dpkg-architecture 是 debian 系特有的,并不通用。 除了 deb 安装器等不考虑像其他发行版移植的应用,不要在 CMakeList 里使用 dpkg 系列命令。

    CMAKE_L_FLAGS

    部分项目 使用了 CMAKE_L_FLAGS,这不是 cmake 标准的变量,这是想用 CMAKE_EXE_LINKER_FLAGS ?

    glib-compile-schemas

    gsettings schemas 安装后需要编译一下(一般目录是 /usr/share/glib-2.0/schemas)才能使用,这个应该是安装后进行的,用对应目录所有 gschema.xml 再编译出一个 gschemas.compiled 文件。

    像下面这样在 CMakeList.txt 编译是不需要的: install(CODE "execute_process(COMMAND glib-compile-schemas ${CMAKE_INSTALL_PREFIX}/share/glib-2.0/schemas)") 相关修改

    可执行程序

    判断某个可执行程序是否存在

    不推荐做法: 根据硬编码路径判断某个文件是否存在。比如判断 QFile().exists(/usr/bin/Foo)

    推荐做法:根据 PATH 寻找可执行文件, 一般不需要自行读取 PATH,比如 QT 可以使用:

    Exist = !QStandardPaths::findExecutable("Foo").isEmpty();
    

    比如 glib 可以使用 find_program_in_path。

    修改示例:

    执行某个可执行程序

    同样,推荐用 PATH 寻找,尽量不使用绝对路径。

    Qt 的 QProcess 会自动处理 PATH 环境变量,因此 QProcess::execute("/usr/bin/touch", QStringList() << sessionCacheFile) 可以直接改成 QProcess::execute("touch", QStringList() << sessionCacheFile)

    修改示例:

    动态库路径

    尽量不要硬编码 /urs/lib

    qt 应用可以使用 QLibraryInfo::location(QLibraryInfo::LibrariesPath)

    头文件

    绝对不要有 #include </usr/include/xxx.h> 这种代码,c/c++ 头文件会自动在 /usr/include 里寻找,直接使用 #include <xxx.h>

    此外,如果库提供了 pkg-config 文件,提供的 -I 参数会指定头文件位置,如果提供 Config.cmake (cmake用)文件,一般会提供 Foo_INCLUDE_DIR 变量,如果提供 .pri (qmake 用)文件,由 QT.foo.includes 提供头文件位置。xxx.h 是相对提供的路径寻找。

    对于没有提供任何开发文件的库,也可以使用 cmake 提供的 CheckIncludeFile 模块寻找头文件。

    应用数据(/use/share)

    /usr/share,一般保存与架构无关的只读数据。此目录同样不应该硬编码,在 XDG 规范中,这种数据的目录使用环境变量 XDG_DATA_DIRS 设置,如果读不到对应环境变量再去读取 /usr/local/share:/usr/share。

    使用 qt 的应用该类型硬编码路径应该使用 QStandardPaths 或者 libqtxdg 代替。

    参考:XDG Base Directory

    在此目录寻找某文件,推荐使用 QStandardPaths::standardLocations 参考修改:

    /usr/share/applications

    直接使用 QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)

    相关修改:

    /usr/share/AppName

    读取本应用数据,qt 软件可以使用 QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation)

    相关修改:

    导入翻译

    部分应用导入翻译使用了 /usr/share 示例, 应该改为使用 QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation), 此外使用了 dtk 的应用是直接用 DApplication 的 loadTranslator 导入翻译。

    Shebangs

    #!/bin/bash -> #!/usr/bin/env bash
    #!/usr/bin/python -> #!/usr/bin/env python
    

    并非所有系统都有 /bin 目录和 /usr/bin 目录,比如 NixOS 和 GNU Guix 操作系统。 env 可以根据环境变量 $PATH 寻找可执行程序(如 bash),比硬编码路径更加灵活,可以提高可移植性。

    不过并非所有脚本都适合使用 env, 有 2 种情况:

    • 对安全性要求高的脚本(如以 root 权限运行),硬编码可以避免通过相关 PATH 让 bash 指向恶意程序。
    • 带有参数的 Shebangs 头,如 #!/usr/bin/perl -w 不能改成 #!/usr/bin/env perl -w

    对除此之外的大部分的 Shebangs 脚本来说,使用 env 是更好的方案。

    参考资料:

    相关 issues:

    使用 GNUInstallDirs.cmake模块

    在指定安装路径时,应当使用变量而非写死安装目录,以便于在不完全符合 FHS 的系统上安装,提高程序的可移植性。对于使用何种变量, GUN 提出了适用于 unix 系统的 GNU标准安装目录,GNU/Linux 上使用的就是这套标准的变体。cmake 官方提供了 GNUInstallDirs 模块,定义了一组标准的变量,用于安装不同类型文件到规范指定的目录中。

    要使用这个模块,在 CMakeLists.txt 添加一行 include(GNUInstallDirs) 即可导入。如果你发现 CMAKE_INSTALL_XXXX 的值为空,大概率是缺少这一行。注意导入模块需要放在使用变量之前。

    前缀值 CMAKE_INSTALL_PREFIX

    CMAKE_INSTALL_PREFIX(后面简称 PREFIX) 是一个非常特殊的变量,在 CMakeLists.txt 中所有的相对路径都会默认把 PREFIX 作为前缀进行拼接,组成绝对路径。这一变量是 cmake 基础变量,不导入 GNUInstallDirs 模块也会存在。

    举一个例子,假如之前要把文件安装进 /usr/share 目录

    install(FILES ${QM_FILES} DESTINATION  /usr/share/deepin-calculator/translations)
    

    如果设置了 PREFIX=/usr,只需要改成相对路径 share,cmake 会自动拼接成 /usr/share

    install(FILES ${QM_FILES} DESTINATION  share/deepin-calculator/translations)
    

    这样写,如果安装前缀改变,比如改成 /usr/local,只需要修改 CMAKE_INSTALL_PREFIX,而不用改 CMakeLists.txt 源码。

    目前 CMAKE_INSTALL_PREFIX 按 GNU 的标准默认值是 /usr/local。而在 dde 的项目中,一般期望前缀值是 /usr。我们当然可以通过 cmake -DCMAKE_INSTALL_PREFIX=/usr 来修改 PREFIX,但最好在项目中添加下面 3 行修改一下 PREFIX 的默认值:

    if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
        set(CMAKE_INSTALL_PREFIX /usr)
    endif ()
    

    这里容易犯两种错误:

    1. 没有检查 CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT 就直接 set:这会导致用户传入的参数失效,相当于硬编码了路径。
    2. 使用 if (NOT DEFINE CMAKE_INSTALL_PREFIX ) 判断用户是否传参了 PREFIX:实际上 PREFIX 无论什么情况都是有定义的, 只能使用 CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT 判断是否是默认值。

    对 CMAKE_INSTALL_PREFIX 的配置会对子目录生效,在 cmake 3.14 之前,先应用安装规则再处理 add_subdirectory,而 3.14 及后续版本,会按照声明的顺序运行所有安装规则/add_subdirectory,因此,除非你想给子目录单独配置,上述的设置应写在 add_subdirectory 的前面。 见 CMP0082

    GNUInstallDirs 变量

    在 GNUInstallDirs 中,定义了一些 GNU 标准安装目录的变量,提供给定类型文件的安装路径。这些值可以传递给对应 install() 命令的 DESTINATION 选项。它们通常是相对于安装前缀(PREFIX)的相对路径,以便于以可重定位的方式将其拼接为绝对路径。当然,它们也允许赋值为绝对路径。

    CMAKE_INSTALL_XXXX

    • CMAKE_INSTALL_BINDIR 用户可执行程序( bin

    • CMAKE_INSTALL_SBINDIR 系统管理员可执行程序( sbin

    • CMAKE_INSTALL_LIBEXECDIR 可执行库文件( libexec

    • CMAKE_INSTALL_SYSCONFDIR 单机只读数据/read-only single-machine data( etc

    • CMAKE_INSTALL_SHAREDSTATEDIR 架构无关的可修改数据/modifiable architecture-independent data ( com

    • CMAKE_INSTALL_LOCALSTATEDIR 单机可修改数据/modifiable single-machine data( var

    • CMAKE_INSTALL_RUNSTATEDIR 3.9版中加入:运行时可修改数据( LOCALSTATEDIR/run

    • CMAKE_INSTALL_LIBDIR 目标代码库/object code libraries ( liblib64 ) (在 Debian 上当 PREFIX 是 /usr 时,LIBDIR 也可能是 lib/<multiarch-tuple>

    • CMAKE_INSTALL_INCLUDEDIR C语言头文件( include

    • CMAKE_INSTALL_OLDINCLUDEDIR non-gcc 的C语言头文件( /usr/include

    • CMAKE_INSTALL_DATAROOTDIR 与架构无关的只读数据根目录( share

    • CMAKE_INSTALL_DATADIR 与架构无关的只读数据( DATAROOTDIR

    • CMAKE_INSTALL_INFODIR info 文档( DATAROOTDIR/info

    • CMAKE_INSTALL_LOCALEDIR 与语言相关的数据/locale-dependent data( DATAROOTDIR/locale

    • CMAKE_INSTALL_MANDIR man 文档( DATAROOTDIR/man

    • CMAKE_INSTALL_DOCDIR 文档根目录( DATAROOTDIR/doc/PROJECT_NAME

    需要注意的是 DATAROOTDIR 是 DATADIR,LOCALEDIR,MANDIR 和 DOCDIR 共同前缀,不应该在 install 中直接使用 DATAROOTDIR 作为参数,而是应该使用 DATADIR 代替 。类似 /usr/share/man 的路径应该用 MANDIR 代替,而不是 DATADIR/man

    CMAKE_INSTALL_FULL_XXXX

    CMAKE_INSTALL_BINDIR 推荐用相对路径,但也可以使用绝对路径,有些时候我们需要绝对路径(比如生成 pkgconfig 文件时),直接加 PREFIX 拼接并不好(因为 BINDIR 可能已经是绝对路径了),这时,我们可以直接使用 CMAKE_INSTALL_FULL_BINDIR,这是由 GNUInstallDirs 模块提供,自动从 BINDIR 计算出的绝对路径。如果 BINDIR 是绝对路径,直接相等。如果 BINDIR 是相对路径,则等于 BINDIR 按照一定规则与 PREFIX 拼接而成的值。

    用例: https://github.com/linuxdeepin/dde-dock/pull/556

    当然,install 是没有必要用 CMAKE_INSTALL_FULL_XXXX 的(当然用也可以),因为它会自动判断,并拼接相对路径。

    前缀拼接的特殊情况

    GNUInstallDirs 前缀拼接存在一些特殊情况需要注意:

    1. CMAKE_INSTALL_PREFIX=/

    除了 SYSCONFDIR, LOCALSTATEDIRRUNSTATEDIR 正常拼接外, 其他值都会增加一个 usr/ 前缀 。比如 INCLUDEDIR 默认值 include ,变成 usr/include, 最终拼接成 /usr/include。

    这里自动增加 usr 是符合 GNU 目录标准的,因为这些路径是符号链接。
    ~ ❯❯❯ readlink /lib
    usr/lib
    ~ ❯❯❯ readlink /bin
    usr/bin
    

    2. CMAKE_INSTALL_PREFIX=/usr

    SYSCONFDIR, LOCALSTATEDIRRUNSTATEDIR 拼接时只拼接 “/”。 比如, SYSCONFDIR 默认值 etc 会拼接成 /etc。

    3. CMAKE_INSTALL_PREFIX=/opt/…

    SYSCONFDIR, LOCALSTATEDIRRUNSTATEDIR 会向后拼接。 比如, SYSCONFDIR 会变成 /etc/opt/….

    参考文档

    Sunday, August 14, 2022

    DDE 控制中心自启动管理插件可单独编译、集成至 DDE 控制中心、集中展示并管理Deepin系统中软件开机自启动设置。

    参赛信息

    本项目参加2023全国大学生计算机系统能力大赛操作系统设计赛-功能挑战赛,选题为proj223-control-center-startup-management-plugin

    参赛小组:

    • 复旦大学 朱元依
    • 复旦大学 沈扬
    • 复旦大学 朱俊杰

    指导老师:

    • 复旦大学 张亮
    • 复旦大学 陈辰
    • 企业导师 王子冲

    完成情况

    1、DDE 控制中心自启动管理插件

    任务完成情况
    (必须)完成一个控制中心插件,能够展示当前所有开机启动项的列表完成
    (必须)插件以单独的仓库提供,并能够单独构建,不需要合并入 dde-control-center 项目完成
    (必须)能够在插件中,通过用户界面的交互来管理(添加、删除、启用、禁用)开机启动项完成
    (必须)编写博客,记录开发过程的心得与体会,并将博客投递至 planet.deepin.org完成博客编写,待deepin官方审核

    成果展示

    功能展示视频链接:

    链接:https://pan.baidu.com/s/1YgvhbZ8pypE4CFhuBYw3eg 提取码:rwcc

    插件图标:

    前端页面

    自启动管理窗口:

    自启动管理页面

    添加自启动应用选择界面:

    应用选择页面

    部署方式

    1、系统环境

    开发环境:Deepin V23Beta版

    系统架构:x86

    镜像下载链接:https://mirrors.ustc.edu.cn/deepin-cd/releases/23-Beta/

    虚拟机平台:WMware Workstation 16Pro

    操作系统环境搭建参考博客:https://blog.csdn.net/qq_44133136/article/details/105887560

    2、配置 Deepin 插件环境

    安装依赖包:

    sudo apt build-dep .
    sudo apt install -y qt5-default
    sudo apt-get install dde-control-center-dev
    

    3、安装插件

    sudo sh install.sh
    

    安装成功后,打开控制中心,会看到以下自启动插件图标,即为安装成功:

    控制中心插件图标

    此时,如果进入/usr/lib/x86_64-linux-gnu/dde-control-center/modules/文件夹,看到编译出的.so文件已经被下载到该文件夹中:

    插件安装位置文件夹

    4、卸载插件

    sudo sh uninstall.sh
    

    重启控制中心,可以看到原本的“自启动管理”图标消失,即为卸载成功。

    分工

    朱元依:插件前端开发

    沈扬:环境配置,编写配置文件

    朱俊杰:后端接口开发(添加、删除、启用、禁用)

    开发计划

    第一步(6/26~7/8)

    • 调研DDE Control Center框架等相关内容
    • 设计前端界面
    • 分工

    第二步(7/9~7/15)

    • 编译教程中的Hello World控制中心插件
    • 设计插件架构

    第三步(7/16~7/22)

    • 配置环境,编译V20示例插件
    • 设计后端接口
    • 修改windowoperationcategory下的文件

    第四步(7/23~7/29)

    • 配置环境,编译Default-AppSelf Start-up插件
    • Debug

    第五步(7/30~8/10)

    • 修改翻译、文字颜色问题
    • 撰写文档

    Wednesday, August 10, 2022

    什么是REUSE

    REUSE是一个工具,准确的来说是一个帮助我们下载开源许可证书和检查开源声明是否合规的脚本:

    使用REUSE配置项目

    安装

    REUSE依赖于本地的python环境,所以需要首先安装python和pip

    sudo apt install python3
    curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
    sudo python3 get-pip.py
    

    然后安装REUSE

    pip3 install --user reuse
    
    

    在此之后,确保它~/.local/bin在您的$PATH中。在 Windows 上,您的环境所需的路径可能类似于 %USERPROFILE%\AppData\Roaming\Python\Python39\Scripts,具体取决于您安装的 Python 版本。

    使用

    初始化项目

    进入一个项目,使用reuse init命令可以初始化项目,此时在你的命令行菜单上会以交互的方式询问你需要的许可证和版权所有人以及邮箱。注意:许可证的名称需要规范:许可证名称

    如果你在这一步遗漏了也没事,可以使用reuse download <许可证名称>的方式下载所需的许可证,这些许可证会一并放入项目下的License文件夹中

    为代码文件添加版权声明

    使用自动化脚本添加

    含有代码逻辑文件添加版权声明方法为:

    reuse addheader --copyright="UnionTech Software Technology Co., Ltd." --license=GPL-3.0-or-later -r <目录>
    

    这只是一个简单的例子,本质上resuse还有更多的用法:

    --copyright-style可以将默认值更改 SPDX-FileCopyrightText为以下样式之一:

    spdx:           SPDX-FileCopyrightText: <year> <statement>
    spdx-symbol:    SPDX-FileCopyrightText: © <year> <statement>
    string:         Copyright <year> <statement>
    string-c:       Copyright (C) <year> <statement>
    string-symbol:  Copyright © <year> <statement>
    symbol:         © <year> <statement>
    

    reuse不会限制你使用多少个copyright 也不会限制你使用的license的个数。

    reuse 会猜测你使用的语言,并且添加对应的注释格式,如果不能正确添加或者需要更多的需求可以考虑下面的方法。

    手动进行添加

    reuse允许你手动进行添加版权声明,只要你的版权声明符合它的规范即可。你所需要的是:充分的耐心,键盘上未曾损坏的Ctrl C V 三个按键即可完成这一切。我个人建议是用上面提供的自动化脚本添加一个版权声明作为模板,然后自己修改下,就可以展现你作为CV工程师的实力了。

    自动进行添加

    理论上reuse不限制你添加版权声明的方式,你可以选择你喜欢的脚本语言来完成这一切。

    为非代码文件添加版权声明

    在项目中是存在非代码文件,比如图片,音频等等。部分也是受到版权保护的,但是我们也没法直接在上面操作(别和我说水印和音频水印,这俩玩意不能无损添加)所以我们要使用一个外置的dep5文件去声明这些非代码文件的版权,dep5文件在执行项目init之后就已经自动帮你生成好了,所以直接写就是的了。比如:

    # css
    Files: *.css
    Copyright: None
    License: CC0-1.0
    

    第一行就是注释 ,第二行是适合的文件,第三行是版权信息(如果使用cc0,则填写None) ,第四行是使用的licence。第一行的注释你可以选择不写,但是为了以后的可读性,强烈建议你写上,甚至可以作为分类的标签。

    第二行文件,支持使用通配符进行模糊匹配,但是不支持正则表达式:仅支持,*匹配任意多个字符 匹配一个字符。所以可以利用通配符来制定匹配某一个文件夹下面的所有文件

    例子:

    # assets
    Files: styleplugins/dstyleplugin/assets/*
    Copyright: UnionTech Software Technology Co., Ltd.
    License: GPL-3.0-or-later
    

    这个例子展示了有版权信息的情况

    例子:

    # png svg
    Files: platformthemeplugin/icons/* styleplugins/chameleon/menu_shadow.svg styles/images/woodbackground.png
           styles/images/woodbutton.png tests/iconengines/builtinengine/icons/actions/icon_Layout_16px.svg
           tests/iconengines/svgiconengine/icon_window_16px.svg
    Copyright: None
    License: CC0-1.0
    

    这个例子展示了为多个文件路径及文件配置版权信息

    强烈推荐你使用路径和具体文件名的方式来制定版权信息,而要避免大范围使用通配符的情况

    例子:

    Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
    Upstream-Name: qt5integration
    Upstream-Contact: UnionTech Software Technology Co., Ltd.  <>
    Source: https://github.com/linuxdeepin/qt5integration
    
    # README
    Files: README.md CHANGELOG.md
    Copyright: None
    License: CC0-1.0
    
    # assets
    Files: styleplugins/dstyleplugin/assets/*
    Copyright: UnionTech Software Technology Co., Ltd.
    License: GPL-3.0-or-later
    
    # Project file
    Files: *.pro *.prf *.pri
    Copyright: None
    License: CC0-1.0
    
    # css
    Files: *.css
    Copyright: None
    License: CC0-1.0
    
    # qrc
    Files: *.qrc
    Copyright: None
    License: CC0-1.0
    
    # png svg
    Files: platformthemeplugin/icons/* styleplugins/chameleon/menu_shadow.svg styles/images/woodbackground.png
           styles/images/woodbutton.png tests/iconengines/builtinengine/icons/actions/icon_Layout_16px.svg
           tests/iconengines/svgiconengine/icon_window_16px.svg
    Copyright: None
    License: CC0-1.0
    
    # sh
    Files: tests/test-recoverage-qmake.sh
    Copyright: None
    License: CC0-1.0
    
    # ignore git
    Files: .git*
    Copyright: None
    License: CC0-1.0
    
    # xml toml json conf yaml
    Files: *.xml *.toml *.json *conf *.yaml
    Copyright: None
    License: CC0-1.0
    
    # rpm
    Files: rpm/*
    Copyright: None
    License: CC0-1.0
    
    # debian
    Files: debian/*
    Copyright: None
    License: CC0-1.0
    
    # Arch
    Files: archlinux/*
    Copyright: None
    License: CC0-1.0
    

    无需版权声明文件的处理方案

    有部分文件是无需版权声明的,比如说某些资源文件脚本文件或者序列化的文件,这种文件有两种处理方式:

    • .gitignore 是用来忽略某些与项目无关的文件和编译过程中产生的文件,同样的如果reuse也会忽略在.gitignore中标记的文件
    • dep5 在执行项目init之后,会在项目目录下产生.reuse/dep5 文件,打开后能看到官方给的范例,如果一个文件不需要特殊版权声明则使用CC0-1.0(会放弃对此文件的所有版权)

    检查项目合规

    reuse lint
    

    这个命令会列出项目文件数,如果项目已经合规,将会以:-)提示,如果项目有些文件不符合开源许可证规范将会以:-(提示,此时你就需要按照上面的提示进行相应的修改即可。

    一般情况下,每个文件都需要有与之相关的版权和许可信息。REUSE规范详细说明了几种方法。总的来说,有这些方法:

    • 将标签放在文件的标题中。
    • 将标签放置在与.license文件相邻的文件中。
    • 将信息放入 DEP5 文件中。

    如果发现一个文件没有与之关联的版权和/或许可信息,则该项目不合规。

    个人建议

    个人建议如果对现有项目使用reuse配置且需要保留版权的时间信息,不要使用一键配置版权头的方式,而是进行手动替换,这里推荐使用vscode的文件筛选功能,筛选出你需要添加的文件,然后cv下去。在配置dep5文件的时候,一定小心通配符的范围,不宜过大。并且对于第三方版权文件一定得小心规避,同样需要完整保留第三方版权信息。

    统信软件常见Dep5书写参考

    参考详细见:

    Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
    Upstream-Name: dtkgui
    Upstream-Contact: UnionTech Software Technology Co., Ltd. <>
    Source: https://github.com/linuxdeepin/dtkgui
    
    # ci
    Files: .github/* .gitlab-ci.yml
    Copyright: None
    License: CC0-1.0
    
    # gitignore #
    Files: .gitignore
    Copyright: None
    License: CC0-1.0
    
    # json conf yaml
    Files: *.json *conf *.yaml
    Copyright: None
    License: CC0-1.0
    
    #interface
    Files: src/util/D* src/kernel/D* src/filedrag/D*
    Copyright: None
    License: CC0-1.0
    
    # rpm
    Files: rpm/*
    Copyright: None
    License: CC0-1.0
    
    # debian
    Files: debian/*
    Copyright: None
    License: LGPL-3.0-or-later
    
    # Arch
    Files: archlinux/*
    Copyright: None
    License: CC0-1.0
    
    # README&doc
    Files: README.md doc/src/*.qdoc
    Copyright: None
    License:  CC-BY-4.0
    
    # DBus
    Files: src/dbus/*.xml
    Copyright: None
    License: CC0-1.0
    
    # Project file
    Files: *.pro *.prf *.pri *.qrc *CMakeLists.txt
    Copyright: None
    License: CC0-1.0
    
    # svg
    Files: src/util/icons/actions/*  src/util/icons/icons/* src/util/icons/texts/*
      tests/images/logo_icon.svg
    Copyright: UnionTech Software Technology Co., Ltd.
    License: LGPL-3.0-or-later
    

    常见开源协议选择

    一般情况下对于文档信息使用CC-BY-4.0 对于我们的一般开源项目使用LGPL-3.0-or-later许可证,如果遇到第三方文件,则是需要保留原有许可证,保留原版版权声明。