开发说明

  关于环境配置,详见运行。当前没有其它的面向最终用户的说明。

  以下为面向开发者的说明。

准备

  YSLib 项目文档位于 YSLib 项目而不是本 wiki 项目中。

  维护者参考的细节和一般规则详见 YSLib 项目文档 doc/ProjectRules.txt ;术语的完整定义详见 YSLib 项目文档 doc/CommonRules.txt

项目过程

  基本规则参见 YSLib 项目文档 doc/ProjectRules.txt

  本 wiki 项目作为用户手册和开发者补充文档的形式作为实现及之后阶段输出。

  实现的附加输出为库和工具。

  之前阶段(如设计)为前期过程,其文档和适用于维护过程的项目规则位于 YSLib 项目文档(位于 doc/ )。

  其中整体过程由 doc/Designation.txt 指定。当前内联设计以外的过程,因此不存在设计外前期过程的单独文档。

开发

平台

  关于外部依赖平台目标平台宿主平台等概念的一般定义参见术语概要

  YSLib 项目约定一个体系结构和使用的外部依赖是一个平台。

  YSLib 支持不同的目标平台。类似 ISO C/C++ 的独立实现(freestanding implementation) 和宿主实现(hosted implementation) ,平台分为两类:独立实现平台宿主实现平台。后者存在操作系统的支持而前者没有。

语言使用和实现要求

  本节适用于本项目,不直接限制依赖项和用户程序。项目中特定部分的规则及适用性详见 YSLib 项目文档 doc/ProjectRules.txt

  除脚本(见以下相关章节)外,使用 ISO C++ 作为主要开发语言。

  不使用和 ISO C++03 以后被接受的特性不兼容的特性,包括但不限于:

  • 被取消的特性,如 ISO C++03 后导出模板的 export 关键字。
    • 不限制有条件使用的之后的其它特性,如 export 被作为模块。(当前不使用模块。)
  • 在 ISO C++03 中标记为 deprecated 而在之后版本去除的特性,如 const char 数组类型左值到 char* 右值的转换。
  • 在 ISO C++03 中标记为 deprecated 但在之后版本重新取消 deprecated 的特性,如修饰命名空间作用域声明的 static
  • 实现的 Defect Report ,如 CWG 615
  • 显式排除的特定的语言特性:

  注意即使使用特定模式,一些实现也可能引入之后的 Defect Report 而不保证兼容,如 GCC PR 65890

  关于精确的特性使用规则、具体使用及备选的特性的清单等,详见标准使用(英文)

  以下对语言实现的要求和支持情况适用整个项目。具体内容可能会在未来变动。

基准实现要求

  YSLib 依赖 ISO C++ 独立实现或宿主实现,附加以下要求:

  • 满足标准使用(en-US) 中具体特性的要求。
    • 注释 默认基于 ISO C++11 环境,但并不要求实现完整支持所有特性。
    • 注释 一些特性具有替代实现或者是可选的,而不被依赖。
  • 满足 ISO C++11 [implimits] 建议的最小实现要求。
  • 标准库基于 ISO C++11 定义的宿主实现,并满足以下要求:
    • 注释 语言实现不需要是完整的宿主实现。
    • 提供以下符合标准的整数类型:
      • 定宽整数 std::intN_tstd::uintN_t(其中 N 为 8 、16 、32 或 64 )。
      • 类型 std::uintptr_t
      • 注释 定宽整数在 ISO C++11 中为可选支持。
    • 满足以下实现定义行为的要求:
      • 至少支持 std::placeholders::_7
      • 注释 按 ISO C++11 Annex B [implimits] ,符合标准的下限为 10 ,因此已被上述规则涵盖。
    • 假定用于迭代器的 difference_type 或坐标计算的有符号整数作为显式转换的目标类型且结果不能在范围内表示时,不引起副作用且结果的值不是能在此范围内表示的任意值(即为小于 0 的值)。
      • 注释 一个典型的例子是 std::ptrdiff_t
      • 当前标准中,转换到有符号数的结果由实现定义。
      • WG21 P0907 已提议修改使用补码表示,并在 ISO C++20 采纳,按,符合此要求。
      • 注释 一般的到有符号数转换的由实现定义的行为仍不被依赖。
    • 假定特定类型的特定操作无异常抛出(但不依赖异常规范的行为),当前包括:
      • std::string 的默认构造函数( WG21 N4002 引入了显式指定 noexcept ,仅从标准草案 WG21 N4296 起有效)。
  • 假定被包含在具有外部链接实体的函数体或声明命名空间作用域中外部链接名称的被 ODR 使用(odr-used) 的 lambda 表达式相同。
  • 对宿主环境中程序外部的状态的并发修改不引起未定义行为。
    • 现有操作系统和文件系统提供的接口和实现普遍不能保证避免 TOCTTOU 访问(en-US) 导致的问题。具体修改的结果未指定,但应不直接引起无法预测的程序行为。
    • 除非另行指定,本项目的实现不保证检查外部程序的修改。

  假定 YSLib 实现和用户程序的代码满足以下要求:

  • 假定异常和标准库 RTTI 对象满足 ODR ,即使是在使用动态库的宿主实现中。
    • 但影响用户代码生成的实现的二进制约定(如 ARM64 )且不使用以下错误实现变通的情形除外。
    • 这要求用户代码不依赖影响相关符号可见性而导致 ODR 失效的特性。
      • 例如,这不允许如 dlopen 使用 RTLD_LOCAL 加载具有相关符号的库。
    • 这允许 std::type_info 的比较操作和散列操作的高效实现,并避免一些实现错误。
      • 使用 libstdc++ (libsupc++) 时,需要重定义宏 __GXX_TYPEINFO_EQUALITY_INLINE 和宏 __GXX_MERGED_TYPEINFO_NAMES1
        • 这是对 std::type_info 比较操作的错误实现的变通。
        • 一般在编译器命令行中使用 -U 选项取消可能存在的预定义再使用 -D 选项重新显式定义,以避免预定义宏被重定义。
        • 调整 __GXX_TYPEINFO_EQUALITY_INLINE 对启用 __GXX_MERGED_TYPEINFO_NAMES 是必要的。
        • 注释 __GXX_MERGED_TYPEINFO_NAMES 默认值被修改 以支持上述影响符号可见性的特性。
      • 其它实现暂不使用选项支持,以避免和上述二进制约定冲突。
        • libc++ 不支持另行调整,由实现根据平台环境指定 _LIBCPP_HAS_NONUNIQUE_TYPEINFO
        • 另见以下关于 std::type_info 比较的限制。
    • 链接时除上述二进制约定,假定 std::type_info 相关的符号被共享。
      • 这不允许在实现生成以外显式地隐藏相关的符号(如通过 #pragma 预处理指令或 -fvisibility=hidden 编译器命令行选项)。
      • 这一般要求在兼容 GNU ld 的链接器在命令行选项中使用 --dynamic-list-cpp-typeinfo 等方式导出相关符号(也允许但不要求使用 -rdynamic-export-dynamic )。
  • 不依赖 RTTI 的 std::type_info 对象在未命名命名空间中同名不同实体的比较结果:
  • 假定在标准库宏 NDEBUG 被定义的翻译单元中的代码不违反异常规范。
    • 这允许改进代码生成,如使用 G++ 的 -fno-enforce-eh-specs 选项
    • 程序应不依赖违反异常规范时调用标准库的函数的行为。否则,程序行为未定义。

  YSLib 中具体子项目可要求更严格的实现假定,违反这些假定要求诊断,并可能在不保证支持的构建配置环境下终止构建。需要显式提供诊断时,若可行,应使用符合上述要求的可移植特性。此外,YSLib 实现可依赖可选的语言实现扩展。具体的更严格的假定和可选特性使用参见项目版本库中 doc 目录下的相关项目文档。

注释 一些诊断通过语言规则保证而无需显式提供。提供诊断使用的可移植特性的一个例子是 #error 预处理命令。

原理 YBase 对标准库的修正实现要求更严格的关于实现细节的假定。YBase 对标准库的不分替代(如 std::addressof )需依赖更严格的实现特性才能完整实现和标准库相同的保证(尽管不分保证作为 YBase 替代是可选提供的)。依赖更严格的实现假定也允许依赖和向用户代码提供特定平台配置的功能特性,以及提供质量更好的高性能实现。

历史实现要求

  以下要求已被修改或取消。

  • 假定提供撤销标准库未定义行为的保证:
    • 撤销 [res.on.functions]/2.5 对特定不完整类型作为模板实际参数引起未定义行为的限制,包括:
      • 使用默认分配器( std::allocator 的实例)的 std::vectorvalue_type 类型。
        • 这被包含在 WG21 N4510
        • ystdex::pmr::pool_resource 的实现从 b843[2018-11-10] 起依赖这项特性。
        • 从 b863[2019-07-26] 起不使用不完整类型的元素而不再依赖这项特性。
      • 使用默认分配器( std::allocator 的实例)的关联容器(即 std::mapstd::set )的 value_type 类型。
        • std::vectorstd::liststd::forward_listWG21 N4510 引入了不完整类型的支持。这不在此处要求。关联容器的要求预期在未来被添加。
        • 已知 libstdc++ 的实现符合这个条件。
        • 在 YSLib 中仅被 YSLib::ValueNode 通过在 std::map 使用递归的键类型的实现从 b338[2012-09-13] 起依赖。
        • 因为使用 ystdex::map 替代 std::map ,从 b830[2017-08-11] 起取消这个要求,不再依赖标准库实现提供的扩展特性。
  • 假定特定类型的特定操作无异常抛出(但不依赖异常规范的行为),包括:
    • std::function::swapLWG 2062 起有效)。
      • 因为使用 ystdex::function 替代 std::function ,从 b848[2018-12-24] 起取消这个要求。

可选实现支持

  允许使用 ISO C++11 以后兼容最新标准草案的正式标准中的特性(可通过 __cplusplus 宏和 SG10 建议的特性检查判断)。

扩展特性

  不依赖实现的方言扩展,但在确保实现能支持时,在特定的代码中可通过条件包含等方式选用。

  若实现默认具有不符合标准的特性,在本项目的代码中不依赖这些特性,即便外部依赖项可能对此进行配置(如 MinGW G++ 为了和 Microsoft VC++ 兼容启用的 -mms-bitfields ,而 MSYS2 安装的 freetype2 的 pkg-config 的 CFLAGS 隐含此参数)。

保留名称

  YSLib 项目中,除了 YBase.LibDefect 是对标准库实现的修正外,并不是语言的实现,因此公开接口遵循 ISO C++ 对保留名称的使用,如不引入以 __ 起始的标识符。

  对实现环境已经以保留标识符提供的接口,适用以下规则:

  • <ydef.h> 提供宏包装特定实现的标识符。
  • 除标准预定义的(如 __cplusplus )和用于特性检查的标识符(以 __cpp__has 起始),以及上述被包装时的宏定义,不在注解(作为宏 YB_ATTRYB_ATTR_STD 的参数)外直接使用保留标识符。

  <ydef.h> 和其它一些 YSLib 项目头文件保留特定的不被标准保留的标识符,详见 YSLib 项目文档 doc/Definitions.txt

库概述

  YSLib 项目由多个子项目组成。其中主要的有顶级子项目:YBase 和 YFramework 。它们是开发 YSLib 应用的必备的库。每个库被构建为单独的映像(静态库或动态库)。

  YSLib 的组件有些是依赖于特定平台的,但更多是平台中立的。关于库的组件在此的不同,详见下文的解释。

  静态库、动态库或其它可能被库构建时依赖的输入以及构建使用的工具是库构建的依赖项。关于依赖项的一般说明,详见术语中关于依赖管理的说明。

平台模拟

  除非另行指定,文档中的狭义的“模拟”概念指程序模拟。

  YSLib 项目中,平台模拟(platform emulation) 主要指直接以运行时环境适配层嵌入宿主平台运行时,在具体程序中提供类似被模拟的目标平台的具体特性和接口。

  完整的定义详见 YSLib 项目文档 doc/CommonRules.txt

外部依赖项

  外部依赖项在构建项目的上下文中,指构建时可能使用的外部依赖,是不由本项目维护和单独发布的依赖项。

  YSLib 项目严格使用 ISO C++ 的子集和特定实现的可选的扩展。关于依赖的语言特性,参见以上语言使用和实现要求的说明。

  在所有目标平台上,除了系统库外,外部依赖项是相同的,但可能使用不同的版本,也不一定按相同的配置构建。系统库的概念和 GNU GPLv3system library exception 中的 system library 定义类似,在此指特定平台或操作系统提供运行时支持的、由特定第三方环境提供开发支持的外部依赖,例如提供特定平台的 ISO C++ 标准库部分实现的 libstdc++ 和提供 Windows API 实现的 GDI32 等。

  除了系统库外,一部分外部依赖项可选或必须使用自行构建的(可能被修改的)版本。这些外部依赖项的修改和构建脚本位于版本库的 3rdparty 目录,默认按原始许可证发行。

  YBase 只直接依赖 ISO C++ 标准库。

  YFramework 默认依赖经过修改的 FreeType2 和 FreeImage 。其中前者当前仅修改头文件,经过特别处理和官方发布的直接构建的版本二进制兼容,可以被系统库替换。

  当前版本中,存储库中不同宿主平台对应的静态库文件(后缀名为 .a )的目录如下:

  • YFramework/DS/lib
  • YFramework/MinGW32/lib
  • YFramework/MinGW64/lib
  • YFramework/Android/lib
  • YFramework/Linux/lib

  其中,使用的 FreeImage 静态库对应 YFramework 的 debug 和非 debug 配置,文件名分别为 libFreeImaged.alibFreeImage.a 。这可在同一个目录树中共存。

  (当前 Android 和 Linux 仅支持单一本机体系结构,实际仅测试 Android ARMv7 和 Linux x86_64 。)

  构建 YFramework 时需包含 3rdparty/include 目录的头文件。Sysroot 安装脚本 Tools/install-sysroot.sh 会复制包括上面的头文件在内的文件。

  当前已经使用的详细外部依赖项详见 YSLib 项目文档 doc/Dependencies.txt

注意 版本库历史中包括静态库(.a) 文件,但为减少版本库大小,不再更新且可能移除,使用外部源或自行构建的方式替代。

  可使用以下方式安装外部依赖项:

  在 build 885 之前,版本库历史的对应的宿主平台中的静态库位于以下位置:

  • YFramework/DS/lib
  • YFramework/MinGW32/lib-i686
  • YFramework/Android/lib
  • YFramework/Linux/lib-x86_64

  其中 Android 平台只包括 FreeType2 ,Linux 平台只包括 FreeImage 。其它平台包括 FreeType2 和 FreeImage 库文件。

  其它平台中,只有随其它文件的发布版本包含完整更新,否则压缩包中可能只有其中一个库文件。

  关于自行构建外部依赖项的方法,参见构建说明

模块

  YSLib 项目组织为一个逻辑上的树形结构,其叶节点称为模块。类似文件系统,非叶节点称为模块目录。在源代码中使用模块路径来标识不同的模块,其分隔符为 :: 。在 YSLib 项目下直接划分的顶级子项目的名称仅在必要时出现在模块路径中,完整模块路径一般从次级子项目的名称起始。

  C 和 C++ 源代码的一个模块由以下三种形式之一构成:

  • 一个头文件
  • 一个非头文件的源文件
  • 一个头文件和对应的源文件

  作为公开接口的模块是公开模块。公开模块的头文件在单独的目录中以便部署。

  按 ISO C 和 ISO C++ 规定,C 或 C++ 模块中存在的非头文件的源文件和包含的头文件构成一个 C 或 C++(预处理)翻译单元,简称单元。注意这里包含的头文件不仅限于模块中的头文件。

  关于模块的进一步说明以及模块路径的形式文法和头文件依赖的基本规则,参见 YSLib 项目文档 doc/ProjectRules.txt

文件系统布局

  作为 C++ 项目,YSLib 把每个顶级子项目的源文件和公开模块头文件分别保存在不同的目录中,即 includesource 。非公开模块若有头文件,也位于 source

  特定于目标平台配置的代码会直接位于 平台名 目录下,称为平台扩展。对应的两个目录为 平台名/include平台名/source

  除了平台扩展的内容,文件系统目录和模块目录的每一级对应。平台扩展的模块目录名是对应平台中立部分加上后缀 _(平台名)

  举例:顶级子项目 YFramework 下的次级子项目 Helper 的非平台扩展的源代码在目录 YFramework/include/HelperYFramework/source/Helper 中,它的 DS 平台扩展的源代码位于 YFramework/DS/include/HelperYFramework/DS/source/Helper 中。

  提供平台扩展的次级子项目只有 YCLib 和 Helper 。

  编译项目时包含的头文件是合并的,如编译器命令行 -IYFramework/DS/include -IYFramework/include 在一次编译中同时使用平台中立和特定于平台 DS 的模块的 YFramework 头文件。

YSLib 及其本体

  在 YBase 和 YFramework 分离之前,YSLib 是一整个库。原 YSLib 大部分仍然在 YFramework 中,仍然可称为 YSLib ,是一个 YFramework 下的次级子项目。注意和整个项目名的不同,以下称为 YSLib 库,以示区分。

  YSLib 库中,Adaptor 用于适配特定于具体外部依赖的接口。可以通过修改其中的代码替换外部依赖,包括部分标准库兼容接口。

  其它部分的接口和实现都是严格平台中立且不依赖外部特定接口而变化的,称为本体。本体中提供了 YSLib 的主要功能。

Helper

  若需要开发依赖平台特定的应用,本体接口可能不足,而需要使用平台扩展。此外,可能需要一些便利功能。

  在 YSLib 库之上,Helper 对此类需求提供了一致而灵活的接口。

  若需要更接近特定平台实现的接口,可以使用 YCLib 及其平台扩展。

  YCLib 和 Helper 在宿主实现上都提供了更加丰富的功能。

代码规范

  YSLib 项目维护的代码规范符合 YSLib 项目中的文档约定的规则,包括:

  • doc/CommonRules.txt :一般规则。
  • doc/ProjectRules.txt :项目规则。
  • doc/LanguageConvention.txt :语言使用约定。

  YSLib 中的脚本代码应符合以下相关章节的约定。

代码格式化

  doc/CommonRules.txt 中规定了命名风格和参考的代码格式。由于格式化代码涉及语义分析,并不保证可以完全自动化进行,需要在编码时注意调整。

  以下工具配置可以把其中的主要工作自动化进行:

  • clang-format
    • 可通过 MSYS2 包 mingw-w64-i686-clangmingw-w64-x86_64-clang 安装,以下配置以这里的 3.7 版本为基准测试
    • 配置选项的文档参见这里
    • 通过命令行 -style= 指定使用选项文件 Tools/YSLib.clang-format
    • 注意 至少以下格式需要手动调整
    • 通过命令行 -i 直接编辑文件而不是打印结果到标准输出
    • 命令行示例: find YBase YFramework YSTest -name "*.h" -o -name "*.hpp" -o -name "*. cpp" | xargs clang-format -i -style=file
    • 命令行示例: find YBase YFramework YSTest -name "*.h" -o -name "*.hpp" -o -name "*. cpp" | xargs -i sh -c "clang-format -i -style=file {}"
    • 命令行示例(同时替换 template < ): find YBase YFramework YSTest -name "*.h" -o -name "*.hpp" -o -name "*. cpp" | xargs -i sh -c "clang-format -i -style=file {} && sed -bi 's/template </template</g' {}"
  • astyle
    • 可通过 MSYS2 包 mingw-w64-i686-astylemingw-w64-x86_64-astyle 安装,以下命令行以这里的 2.05.1 版本为基准测试 *(版本 2.05 )命令行选项的文档参见这里
      • --help 取得的文档可能有问题--delete-empty-lines 对应的短选项应为 -xe 而不是 -xd
    • 使用命令行选项 -A1 -T -p -U -k1 -xj -xy -xC80
      • 短选项可以缩写
      • 和上述 clang-format 比较,少了部分功能,主要有
        • AlignAfterOpenBracket: DontAlign
        • AlignTrailingComments: true
        • AlwaysBreakAfterReturnType: All
        • MaxEmptyLinesToKeep: 2
    • 注意 至少以下格式需要手动调整
      • 无法正确识别 constexpr 导致错误的缩进
      • 虽然右值引用识别问题已解决,但实测对模板参数无效
      • extern "C" 块冗余缩进
      • 断行后的缩进
      • lambda 表达式的捕获列表中的 = 周围的冗余空格以及对应的 { 断行
      • static_cast 等关键字后的 <> 周围的冗余空格
      • 宏实际参数列表头部的 ( 和作为第一个参数的标点可能有冗余空格
      • 宏实际参数列表尾部的 ,) 没有以空格隔离
      • 初始化数组的列表 { 和之前的 ] 存在冗余空格
    • 不使用 -xp ,尽管一些注释需要(移除行首 * 后保持一级缩进)的此类格式,但它会不必要地影响大部分 Doxygen 注释块
    • 默认备份文件后缀 .orig ;可选使用 -n 取消备份文件,或 --suffix= 修改备份文件后缀
    • 使用 -r 递归处理子目录
    • 可选使用 -v 显示详细过程
    • 可选使用 -Q 只显示被处理的文件
    • 可选使用 --dry-run 不实际处理文件
    • 命令行示例:astyle -vQnrA1TpUk1xjxyxC80 YBase/*.h* YBase/*.cpp YFramework/*.h* YFramework/*.cpp YSTest/*.h* YSTest/*.cpp

构建选项

  一般地,在脚本中默认指定的工具链的警告选项应能支持代码规范。

脚本

  YSLib 版本库中包含若干脚本。这些脚本和 YSLib 安装部署的脚本使用命令行程序运行环境和本章中的约定。

  脚本一般通过调用解释器运行。脚本的运行要求宿主环境(符合 YSLib 源码 YF_Hosted 定义支持的平台,指具有操作系统的环境)。

  除非另行指定,脚本以文件的形式部署。

外部环境

  • 除非另行指定,脚本不假定自身的存储位置。
    • 一般地,这允许脚本的内容不总是可通过文件系统访问。这包括执行时脚本的来源被移除和不公开以文件的形式部署等情形。
  • 对文件系统中的脚本:
    • 脚本文件总是普通文件或可解析到普通文件的有效的符号链接。
    • 除非另行指定,不假定脚本文件所在的位置或其父路径可写。

  项目中提供的脚本在版本库中具有固定的相对路径。除解释环境适用的公共的约定(如提供一般类 UNIX 系统使用的文件系统布局的 FHS(文件系统层次结构标准)),脚本不预设绝对路径的假定。

警告 小心处理路径。当前脚本直接或间接使用的所有涉及文件系统递归操作均不假设检查遍历的目标之间是否重复,若使用不恰当的目录链接,可能造成未预期的行为(如复制指向父目录的目录链接时可引起无限递归)。部署时应提前确保不存在这样的链接。

  脚本实现假定没有可能冲突的并发文件访问。

脚本文件

  脚本文件总是使用以下约定的扩展名。

  除了 Windows 命令解释器使用的 .cmd 文件以及 NPLA1 脚本使用的 .txt 文件,所有脚本使用 Shebang 明确需要使用的解释环境(详见 shell 脚本的说明)。

  Windows 命令解释器脚本因为实现限制使用本机 ANSI 代码页的编码,约定为兼容 ASCII 。

  NPLA1 脚本约定使用带有 BOM 的 UTF-8 编码(去除 BOM 的脚本可能兼容 ASCII )。详见 stage 1 SHBuild 中关于 NPL 支持的说明

接口约定

  脚本变量可能被读取和加载。

  除非另行指定,脚本引入的非导出的变量和名称以 _ 结尾的变量,都不是公开接口。

原理 基于名称约定便于和构成公开接口的名称区分。

路径

  路径(path) 是由有限的路径组件构成的序列。在不引起歧义时,路径即指文件系统路径。文件系统的路径组件能以字符串形式表示。

  在支持相对路径的环境中,是否支持相对路径取决于具体脚本。

  内部的路径的分隔符使用 / 。通过工具脚本提供的 SHBuild_2w 等函数,可转换为带有不同路径分隔符的路径字符串。这种转换通常仅在必要时(如明确作为外部工具的输入)使用。

  字符串形式的路径(即路径字符串)可能是保证不以分隔符结尾的文件名和确保以分隔符结尾的目录路径字符串。目录路径指目录路径字符串或其对应的非字符串形式的路径。

  两个路径字符串的拼接可构成新的路径字符串。这两个路径字符串分别是路径前缀和路径后缀。除非另行指定,作为路径前缀使用的路径字符串是文件名。这要求通过串接路径后缀构成访问前缀指定的目录的新的路径时,路径后缀需要以分隔符起始。

注意 YBase 的 ystdex::path 和 YFramework 的 YSLib::IO::Path 等数据结构表示根路径外无视结尾分隔符的非字符串形式的路径,因为有效的分隔符仅在根路径中出现。这些实现一般不检查分隔符的合法性,如其中具有包含分隔符的路径组件,也可能正常转换为字符串形式的合法路径。当前脚本只使用字符串形式的路径。

Shell 脚本

  扩展名 .sh 的脚本文件是 shell 脚本文件。大多数脚本需要使用 GNU bash 运行,如:

#!/usr/bin/bash

  需要考虑兼容性时,一般使用以下替代:

#!/usr/bin/env bash

  其它可以直接兼容 POSIX shell 的 .sh 脚本使用 Shebang 如:

#!/usr/bin/sh

  需要考虑兼容性时,一般使用以下替代:

#!/usr/bin/env sh

  为简化脚本代码,用户需要保证调用 shell 脚本时,环境应满足以下条件,否则行为未指定:

  • 使用满足要求的 Shell 语言实现:
    • 对 bash 脚本:
      • 满足版本要求:当前 Bash 最低版本为 4.4 。
      • 不使用 POSIX 兼容模式。
    • 对其它 POSIX shell 脚本:
      • 使用符合 POSIX 的 shell ,或使用以上要求相同的 bash 运行 shell 脚本。
    • 可支持使用特定的公共非 POSIX 扩展:
      • 使用 mktemp 命令创建临时文件名。
      • 注释 这些扩展在应在受支持的平台环境中可用。
    • 注释 除非另行指定,不要求对 shell 环境进行检查。
  • 不论是否设置了变量 POSIXLY_CORRECT ,命令解释环境的变量满足:
    • 特殊内建工具没有被用户定义的同名变量或别名覆盖。
    • 被脚本调用的 POSIX 定义的工具命令没有被用户定义的同名变量或别名覆盖为调用时可观察行为不等价的实体。
    • 变量 IFS 未设置或设置为默认值。
  • 命令解释器同 bash-p 选项启用时效果相同。
    • 用户应保证不设置 CDPATH-p 忽略的环境变量$BASH_ENV 等启动文件,以避免未预期的不同行为。
  • 脚本文件不是目标在不同目录中的符号链接。
    • 对 bash 脚本,允许使用变量 BASH_SOURCE 的值(如 ${BASH_SOURCE[0]}${BASH_SOURCE%/*} 等形式) 较可靠地判断脚本文件的路径。此时,隐含需要附加假定通过脚本文件路径确定其它资源的位置。
    • 部分脚本可能有更进一步的使用限制或不依赖 . 命令及命令解释器调用的差异。此时,可直接使用 "$0" 判断路径。
  • 除非另行指定,调用的外部命令的程序满足以下可用性要求:
    • 以 POSIX 实用程序名称直接调用的满足 POSIX 对应的要求。
    • 当前使用的 shell 的名称(如 bash )总是能被 POSIX env 实用程序搜索到并调用。
    • /usr/bin/env 总是提供可用的 POSIX env 实用程序
      • 注释 这允许上述 Shebang 可用。同时,/usr/bin/env 作为命令名称一般等同于 env
    • 注释 除非另行指定,不使用 which
      • 在 GNU bash 中,对检查一个操作数的情形,使用 type -P 代替。
      • 注意 command -v 对别名等非 $PATH 中可搜索的程序处理不同。
      • 另见 ShellCheck 警告 SC2230相关讨论

  脚本实现中的调用不受以上限制。

  若脚本可以确保兼容 POSIX shell ,使用后者而不是前者。

  如果对应解释器位于其它目录,可以符号链接到上面指定的路径。

  除非另行指定,本文档约定使用的路径字符串的分隔符为 / ,不连续出现在路径中,且不在结尾出现。例外:

  • 平台相关的绝对路径转义可出现 //
  • 构成路径的路径前缀可能以分隔符结尾,直接表示上层目录。

注意 当前版本库中的文件不保证跟踪权限。在一些环境中可能因为可执行权限问题导致无法立即执行脚本,参见这里的说明

  正式支持的发布版本中的公开 Shell 脚本应保证用 ShellCheck 0.7 或以上版本(任选)检查没有诊断消息。检查的命令行为 shellcheck -x -P SCRIPTDIR 跟随文件名。除非另行指定,使用明确的方式避免引起检查的问题而不是使用指令消除检查的结果,如使用 ${BASH_SOURCE[0]} 代替 $BASH_SOURCE 以避免 ShellCheck 警告 SC2128

Shell 语言使用规约

  脚本程序不调整影响别名扩展的 shell 选项(如 bash 的 shopt -u expand_aliases )。

  依照运行时环境的约定,除非另行指定,脚本不区分未设置的环境变量和已设置但具有空值的环境变量,以允许在外部环境配置脚本的执行。这两种情形下,被用于脚本中的变量的环境变量都可能指定一个非空的初始值,称为变量的默认值(default value) 。变量是否使用默认值总在先于第一次使用变量的值的初始化时确定。

  变量的初始化和默认值满足以下约定:

  • 初始化的变量是否只读未指定。
  • 初始化变量时若需使用默认值,可能发生附加的求值(如命令调用)以其结果确定具体默认值。
  • 除非变量的初始化被指定为由特定的函数确定,变量在未指定的脚本被执行时无条件初始化,而不需要使用变量的值前调用特定的函数。
  • 除非另行指定:
    • 在特定函数中初始化的变量由此函数指定其默认值。
    • 初始化时使用的外部环境变量的值可假定和脚本运行环境直接或间接调用脚本前相同。

  除非另行指定,shell 函数满足以下约定:

  • 已定义的 shell 函数假定不被其它变量覆盖。
    • 原理 这允许实现省略 readonly -fdeclare -g -r -f 声明,且允许使用相同的定义覆盖以简化调试。
  • Shell 函数返回值是通过接口语义中蕴含的最后的命令调用的返回值;或当不存在这样的命令时,默认为 0
  • Shell 函数的结果是标准输出的内容。
    • 注释 通常使用 echo 或功能蕴含 echo 的命令调用输出返回的值。

  非生成的脚本源代码中不使用包含 __ 的名称。

  公开函数的函数名及公开的变量名不以 _ 结尾,否则以一个 _ 结尾。

  对 SHBuild 相关工具使用的函数,使用前缀 SHBuild

  函数出错且无法恢复时退出脚本。

  版本库中的提供的脚本一般应能通过 ShellCheck 检查。对需要使用 ShellCheck 指令指定脚本起始构造的检查时,在之前添加一行空命令(对 bash 脚本使用 : ,非 bash 脚本命令使用 true )以避免非预期地使指令作用于整个脚本。

环境变量

  Shell 脚本可使用以下能通过外部环境指定值的环境变量:

  • 用于指定入口位置:
    • SHBuild :外部 SHBuild 可执行文件路径。
      • 默认值和特定脚本被设计适应的部署环境相关(例如,视不同情形,脚本可使用 ${BASH_SOURCE[0]}"$0" )。
    • SHBuild_ToolDir :工具目录中的脚本目录(参见脚本)。
      • 默认值由脚本根据存储库所在目录确定。
  • 用于指定存储库中的资源:
    • SHBuild_BaseDir : SHBuild 目录,是工具目录中提供 SHBuild 的源代码的目录。
      • 默认值按 SHBuild_ToolDir 初始化后的相对位置确定,为 "$SHBuild_ToolDir/../SHBuild"
    • YSLib_BaseDir :YSLib 目录,即版本库检出后的工作目录。
      • 默认值按 SHBuild_ToolDir 初始化后的相对位置确定,为 "$SHBuild_ToolDir/../.."
  • 用于指定输出目录:
    • SHBuild_BuildDir :构建使用的中间输出文件路径。
      • 默认值定义如下:
        • 对不依赖 Sysroot 的以及 YSLib/YSTest 目录下的项目构建脚本,默认值指定在完整的版本库中和 "$YSLib_BaseDir/build/$(SHBuild_GetBuildName)" 相同的目录,且构建 stage 1 YSLib 时确保为绝对路径。
        • YSLib/YDE 目录下的项目构建脚本,默认值是 ".$(SHBuild_GetBuildName)"
        • 否则,默认值是当前工作目录 .
        • 以上调用的函数 SHBuild_GetBuildName脚本 Tools/Scripts/SHBuild-common.sh 中,依赖具有非空值的变量 SHBuild_Host_OSSHBuild_Host_Arch
    • SHBuild_SysRoot :输出的 Sysroot 根路径。
      • 在 stage 1 中,默认值是 "$YSLib_BaseDir/sysroot"
  • 用于配置构建环境:
    • 其它情形不用于指定输出目录,不保证具有默认值。
    • SHBuild_Env_TempDir :构建时使用的临时目录路径。
    • SHBuild_Env_Arch :构建系统架构。
    • SHBuild_Env_OS :构建系统操作系统。
      • 初始化和确定值的方式同 SHBuild_Env_Arch
    • SHBuild_Host_Arch :宿主架构。
      • 由脚本 Tools/Scripts/SHBuild-common.sh 的函数 SHBuild_PrepareBuild 初始化。
      • 默认值使用以下方式指定:
        • SHBuild_Env_OS 的值是 Win32 且外部变量 MSYSTEM 的值是 MSYSTEM64 时,默认值为 x86_64
        • SHBuild_Env_OS 的值是 Win32 且外部变量 MSYSTEM 的值是 MSYSTEM32 时,默认值为 i686
        • 其它情形默认值同 SHBuild_Env_Arch 初始化后的值。
    • SHBuild_Host_OS :宿主操作系统。
      • 初始化的方式同 SHBuild_Host_Arch
      • 默认值同 SHBuild_Env_OS 初始化后的值。
    • SHBuild_VCS_ 起始的变量:指定使用的版本控制系统。
      • SHBuild_VCS_hg :若非空,指定使用 Mercurial
      • SHBuild_VCS_git :若非空,指定使用 Git
      • 除非另行指定,若同时指定使用 Mercurial 和 Git 两者,则使用 Mercurial 。
      • 指定使用版本控制系统是提示,不保证是实际的选择。具体使用前,脚本可能自动对可用性(命令行 hggit 及当前工作目录是否位于对应的版本库)进行检查,当不满足要求时可能忽略提示。
      • 原理 若当前工作目录同时位于多个版本控制系统的版本库,使用提示可以明确选择其中之一。
      • 注释 本项目的源代码可从 Mercurial 或 Git 中取得
    • SHBuild 构建使用的变量:详见 SHBuild 的帮助信息。
    • 其它构建脚本使用的变量:详见以下具体脚本的说明。

  若以上变量在上述任一情形存在作为路径的默认值且外部环境指定变量的值,则指定的值应表示合法的路径。

  一般地,部署的 shell 脚本需引用其它脚本时,以如下方式使用指定入口的环境变量:

  • 若脚本只支持 Sysroot ,以 SHBuild 在 Sysroot 的安装位置推断其它脚本的路径。
  • 若脚本只支持工具目录中的脚本目录布局,以 SHBuild_ToolDir 指定。
    • 这隐含目录在版本库中的布局,可以此初始化 YSLib_BaseDir 等变量的默认值。
  • 若脚本同时支持 Sysroot 中部署的位置和工具目录中的脚本目录布局:则不使用以上的变量。
    • 引用其它脚本文件时可直接指定包含,如:. "$(dirname "${BASH_SOURCE[0]}")/SHBuild-common.sh"

  除指定入口的环境变量,是否支持外部可覆盖默认值的上述环境变量是可选的,取决于各个脚本的具体支持。

  典型地,Shell 指定的环境变量可被以下方式在被 shell 运行的命令中使用:

  关于这些环境变量如何影响其它程序,参见运行时的环境变量

NPLA1 脚本

  NPLA1 脚本可被 SHBuild 调用。SHBuild 支持特定的选项作为 NPLA1 的脚本解释器。脚本解释器支持 NPLA1 脚本文件。此外,在 shell 脚本中,NPLA1 脚本代码可能以字符串的形式存储和被 SHBuild 调用。

NPLA1 环境变量

  NPLA1 脚本可使用和 shell 脚本通用的环境变量,如 SHBuild

  除 stage 1 外,shell 脚本使用环境变量 NPLA1_ROOT 指定的根目录作为 SHBuild 加载 NPLA1 脚本使用的起始目录。

  脚本(包括直接或间接调用 NPLA1 脚本的其它脚本)除默认值外不需要依赖 Sysroot 的安装路径下的目录和文件布局,环境变量 SHBuildNPLA1_ROOT 可分别指定相互无依赖的路径。当仅指定 SHBuild 时,通过指定的 SHBuild 路径推断 NPLA1_ROOT 默认值,此时使用假定符合 Sysroot 约定的相对路径。

  NPLA1 脚本可使用以上约定含义和默认值的 shell 环境变量并初始化 NPLA1 脚本内的同名变量,但部分变量的作用域和初始化方式可能不同:

  • 在 Tools/Scripts/SHBuild-YSLib-common.txt 中直接初始化变量 SHBuild_Env_ArchSHBuild_Env_OS
  • 在 Tools/Scripts/SHBuild-YSLib-common.txt 的函数 SHBuild_GetPlatformStrings(详见版本库中的 doc/NPL.txt)的调用中初始化变量 SHBuild_Host_ArchSHBuild_Host_OS

  注意在 NPLA1 脚本中创建的变量不是环境变量;但和 shell 脚本类似,若需作为其它外部命令的环境变量,可先导出再调用对应的程序。

NPLA1 函数

  NPLA1 脚本可提供部分 shell 脚本中的同名函数。除非另行指定,这些函数的调用接口以及功能和 shell 脚本中的同名函数一致,但实现和以下行为不保证相同:

  • 出错时的诊断方式。
  • 缓存的变量及相关的输出提示信息。
  • 性能。
  • 影响的非公开环境变量等其它外部环境状态。

  作为公开接口的 NPLA1 函数另见 doc/NPL.txt 中的描述。

构建脚本

  一些开发脚本被用于构建。构建脚本的一般形式提供配置和生成阶段的自动化功能。

  配置阶段设置交互环境。典型地,通过执行命令前设置的环境变量指定可配置项。

  生成阶段调用合适的工具完成构建。

  配置阶段的接口可能对外隐藏,此时使用默认配置进行构建。

  一些公用的构建脚本可适用于整个项目。其它的构建脚本可构建具体子项目中的目标。

  构建脚本可以是 makefile 或其它可执行的脚本。其中,makefile 可能使用以上约定含义的 shell 环境变量,但不保证可被外部指定覆盖脚本中指定的默认值。

参考

  • YSLib 项目文档 doc/Dependencies.txt 了解组织结构、开发规则、默认使用的外部依赖项(包括语言实现)和相关约定。
  • 结构和特性 中的树形结构了解项目依赖性。
  • YSLib 项目文档 doc/ProjectRules.txt 了解组织结构、开发规则和相关约定。
  • YSLib 项目文档 doc/YBase.txt 了解顶级子项目 YBase 。
  • YSLib 项目文档 doc/YFramework.txt 了解顶级子项目 YFramework 。
  • YSLib 项目文档 doc/YSLib.txt 了解 YFramework 的次级子项目 YSLib 。