• HOME
  • JOIN
  • RSS
  • Tuesday, December 27, 2022

    vscode snipets 用户代码片段使用指南

    snipets简介

    snipets 我愿意称为程序员的ctrl+c ctrl+v最大的竞争对手,sniptes适用于遇到很多重复代码却又没法去提取代码特点抽象成类的情况。比如写注释的时候。

    这是一个极好的工具用于高效率办公(摸鱼)

    vscode 打开snipets

    然后你就需要选择对应的语言,这里我以doxygen注释文件为例子: 打开之后会得到这样一个文件:

    {
     // Place your snippets for doxygen here. Each snippet is defined under a snippet name and has a prefix, body and
     // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
     // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
     // same ids are connected.
     // Example:
     // "Print to console": {
     //  "prefix": "log",
     //  "body": [
     //   "console.log('$1');",
     //   "$2"
     //  ],
     //  "description": "Log output to console"
     // }
    }
    

    这个文件都是注释组成,自然也没有任何功能,我们要做的是对其进行修改,以支持我们自己的代码片段

    代码片段的解读

    先以一个比较简单的例子作为内容:

    {
     // Place your snippets for doxygen here. Each snippet is defined under a snippet name and has a prefix, body and
     // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
     // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
     // same ids are connected.
     // Example:
     // "Print to console": {
     //  "prefix": "log",
     //  "body": [
     //   "console.log('$1');",
     //   "$2"
     //  ],
     //  "description": "Log output to console"
     // }
     "dox dtkwidget": {
      "prefix": "da",
      "body": [
       "@fn void Dtk::Widget::DAlertControl::${2:function}",
       "@brief ${3:info}"
      ]
     }
    }
    

    我们来逐行解释:dox dtkwidget指的是这个代码片段的命名,你可以使用任何你喜欢的命名来代替它

    然后就是一个键值对 “prefix” 的值一般是一个字符串,这个字符串也是可以自定义的,含义就是你在代码文件里面输入这个字符串(在某一行的开头)后会弹出选项询问你是否使用代码片段:比如这个就是da,你在某一行的开头打出da这俩字符,就会弹窗询问你是否替换

    同时会显示对于代码片段的命名

    body键值对就是最重要的一部分,这个里面就保存的你需要的代码片段,

    请注意,其中每一行都代表的是你之后生成的代码的一行,如上代码片段生成的代码就是

    @fn void Dtk::Widget::DAlertControl::function
    @brief info
    

    如果你现在已经复制了我的代码片段,不妨新建一个dox文件试试。打出da之后会弹出选项,按tab键确认之后就会出现这个代码,不过会让你填写function的值,填写完毕之后就按tab键切换到info需要你填写info的值

    不过这样还不是代码片段的全部实力,它还能更强大点:

     "dox about dtkwidget": {
      "prefix": "dd",
      "body": [
       "@property ${1|QColor,void,bool|} Dtk::Widget::DAlertControl::${2:function}",
       "@brief 返回${3:info}",
       "",
       "@fn void Dtk::Widget::DAlertControl::set${2/(.*)/${1:/capitalize}/i}",
       "@brief 设置${3:info}",
       "@sa DAboutDialog::${2:function}"
      ]
     }
    

    比如这样

    你可以自己试试这个会发生什么

    对的 它还支持正则表达式的替换

    现在看看我正在用的doxygen的代码片段

    {
    
     "dox about DtkGui": {
      "prefix": "dd",
      "body": [
       "@property Dtk::Gui::DDciIconPalette::${2:function}",
       "@brief ${2}属性",
       "@sa READ方法: void Dtk::Widget::DDciIconPalette::${2:function}",
       "@sa WRITE方法: void Dtk::Widget::DDciIconPalette::set${2/(.*)/${1:/capitalize}/i}",
       "",
       "@fn ${1|QColor,void,bool|} Dtk::Widget::DDciIconPalette::${2:function}",
       "@brief 返回${3:info}",
       "@sa DDciIconPalette::${2:function}",
       "",
       "@fn void Dtk::Widget::DDciIconPalette::set${2/(.*)/${1:/capitalize}/i}",
       "@brief 设置${3:info}",
       "@sa DDciIconPalette::${2:function}"
      ]
     },
     "dox dtkwidget": {
      "prefix": "da",
      "body": [
       "@fn void Dtk::Widget::DDciIconPalette::${2:function}",
       "@brief ${3:info}"
      ]
     }
    }
    

    我可以通过这个代码片段一键生成属性的get和set方法的doxygen注释

    Monday, December 12, 2022

    自小组成立以来,已经为 DDE 的移植做出了很多贡献,本文做出了一些总结。如有补充或批改,可向 sig-dde-porting 提出 pr。

    NixOS 的移植从今年 3 月开始,到现在已经有了实用的可能,今后会用一篇文章单独介绍。未来会每月更新进展,关注的同学可以订阅本站 rss。

    今年改善可移植性的一些努力

    • 修复依赖特定 kwayland 无法在其他发行版编译的问题(为项目增加禁用wayland的option):

    deepin-system-monitor,dde-clipboard,dde-session-shell…

    • 修复安装路径硬编码问题

    29 个相关 pr https://github.com/linuxdeepin/developer-center/issues/3167

    • 减少代码中的硬编码问题

    16 个相关 pr https://github.com/linuxdeepin/developer-center/issues/3374

    • 编译 flag 直接覆盖的问题

    大约 8 处

    • 修复 as-need 参数 break 了部分连接器:

    10 个相关 pr https://github.com/linuxdeepin/developer-center/issues/3345

    • 完善项目关于 pkg-config/Config.cmake 的问题(如写死路径,版本号错误,引入依赖检查不完善等等)

    至少 17 个相关 pr,事实上应该远多于 17,存在多个修改顺便提在一个 pr 的情况。

    • 使用 dpkg-architecture 判断架构其他发行版无法使用问题

    4 处以上

    • 避免绝对路径头文件:

    解决 2 处,但仍有 1 处难以处理(https://github.com/linuxdeepin/dde-session-shell/pull/120)

    • dde-kwin 问题

    justforlxz 已经多次修复 dde-kwin 对 kwin 的适配问题,但每次 kwin 每次更新都会重新带来很多问题。正在移植中的 deepin-kwin 将会解决这个麻烦,目前可以使用 Arch(aur)或者 dde-nixos 先行体验。

    • 处理 deepin-wallpapers 非自由协议问题(拆包)

    dde-nixos 近期进展

    • dtkcore:

      • 其他发行版调用 uosType 直接返回 UosTypeUnknown,避免无意义的报错刷屏
      • 完善 isDDE 函数判断
      • deepin-os-release 判断是其他发行版后不会输出 deepin/uos 特有参数
    • dde-control-center:

      • 提供禁用生物认证模块的 option
      • 修复无法设置头像问题
      • 修复密码校验无法通过的问题
      • 修复系统信息/关于本机的显示问题:
        • 启用不应隐藏的“计算机名”,“产品名称”
        • 隐藏“版本”(社区版/专业版)
        • 替换为 NixOS 的 logo
        • 隐藏修改 “计算机名” 的功能
      • 隐藏用户体验模块,禁用对应 dbus 调用,修复通用模块卡钝问题
      • 修复显示内存为 0 的问题
    • deepin-movie:

      • 修复格式支持不完整的问题(修复 libPath 硬编码)
      • 修复一个崩溃问题
    • dde-grand-search:

      • 修复调用 dde-grand-search-daemon 的权限验证
    • deepin-screen-recorder:

      • 修复2个导致崩溃的问题
      • 修复命令行启动无法录屏的问题(dock插件仍然有此问题)
    • 修复 GStreamer 音频播放:

      • 需要设置依赖以及 GST_PLUGIN_SYSTEM_PATH_1_0
      • 涉及 deepin-music deepin-voice-note deepin-movie 和 dde-introduction
    • deepin-editor:

      • 修复主题路径硬编码造成的界面异常
    • deepin-compressor:

      • 修复无法加载自身插件的问题
    • 修复个别图标丢失的问题(部分 dci 图标需要 qtimageformats 支持)

    Thursday, September 22, 2022

    这是第一篇 deepin-m1 sig 的博客,我非常的激动,因为 deepin 现在已经可以在 m1 设备上启动了。

    目前在 m1 上启动 Linux 的项目进展最快的是 Asahi Linux,他们是 Linux m1 的项目发起者,同时也是 Asahi Linux 的维护者,目前 Asahi Lina 正在努力开发 m1 GPU 驱动,已经实现了绝大部分底层的功能,正在做渲染队列的工作,相信很快就可以为 Linux 带来开源的 m1 GPU 驱动。

    debian 和 fedora 也基于 Asahi installer 制作了发行版本,我们的工作也是从这里开始的。

    经过分析,发现 Asahi installer 已经预留了更换 rootfs 的接口,这使得我们的工作异常顺利,很快便完成了第一版的安装文件。

    bootloader

    start

    目前 m1 启动 Linux 的流程是先启动 Asahi m1n1,由 m1n1 启动 u-boot,再由 u-boot 启动 grub。

    m1n1 是一个非常不错的项目,可以通过 usb 进行串口调试,方便进行驱动逆向和调试,也可以测试启动 elf。

    在 m1 上安装 Linux,需要先进入一次 macOS 的恢复模式,利用恢复模式将文件内容写入,目前这已经是最好的解决方案了,目前小组正在整理相关文档和脚本,预计下个月可以对外提供正式的安装脚本和视频教程。还有很多功能没有准备好,例如一些 x86 软件无法运行,m1 只支持 16k 页内存等,可能会带来很多软件的兼容性问题,我们也欢迎更多的人加入 deepin-m1 sig 组中,提供更多的帮助。

    enjoy live!

    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: