开发说明

  关于环境配置,详见先决条件运行。其中后者也包含部分面向最终用户的说明。

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

准备

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

注释 项目文件在 YSLib 存储库中维护。

项目过程

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

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

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

  之前阶段(如设计)为前期过程,其文档和适用于维护过程的项目规则位于 YSLib 的项目文档中的 doc/ 目录。其中整体过程由 doc/Designation.txt 指定。当前内联设计以外的过程,因此不存在设计外前期过程的单独文档。

开发

平台

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

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

注释 这仍然满足关于平台标识默认的使用约定

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

语言使用和实现要求

  本节适用于 YSLib 项目,不直接限制依赖项和用户程序。项目中特定部分的规则及适用性详见 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 的默认构造函数。
        • 容器类型的迭代器的复制初始化。
          • 原理 这允许使用迭代器类型作为函数的参数时,不影响调用者的异常安全保证。
          • 注释 包括可能被显式定义的类的复制构造和转移构造函数。
          • 注释 已知 libstdc++ 、libc++ 和 Microsoft STL 的当前实现(蕴含所有被支持的平台配置)的非调试版本都满足这些要求且具有无异常抛出的异常规范。因为 NullablePointer 的操作满足无异常抛出保证,使用这些类型直接实现的迭代器的操作满足这里的要求。
  • 假定被包含在具有外部链接实体的函数体或声明命名空间作用域中外部链接名称的被 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 替代是可选提供的)。依赖更严格的实现假定也允许依赖和向用户代码提供特定平台配置的功能特性,以及提供质量更好的高性能实现。

注释

  非全局的特性涉及的假定可能以子项目或更小的模块作为单位明确其适用范围。关于可选指定的实现要求,参见以下的可选实现要求

可选实现要求

  以下相对基准实现要求更严格的实现要求默认不作为全局默认要求,而可供特定的子项目和平台配置按需启用。具体使用参见存储库中 doc/ 目录下有关具体子项目的项目文档。

  • 关于整数类型的假定:
    • 1 字节具有 8 位(即 CHAR_BIT == 8 )。
    • 相对 ISO C++ 要求更严格的特定整数类型的取值范围。
    • 语言实现和外部环境满足以下表示的要求:
      • 除指定的例外,整数类型的表示不具有填充位(padding bit) ,即构成其对象表示(object representation) 的位和值表示(value representation)一致,但这里指定的例外除外。
        • 注释 关于填充位,参见 ISO C 关于整数类型的描述。
        • 指定的例外包含以下情形:
          • (可能 cv 限定的)bool 类型。
          • 语义和表示可能兼容 ISO C23 _BitInt(N) 的类型。
            • 注释 一个例子是 LLVM 扩展 _ExtInt(N)
          • 其它在 YSLib 中的 API 或互操作规范中指定的允许接受的特定整数类型。
  • 关于标准库实现的附加假定:
    • 假定 std::chrono::system_clock 使用 1970-01-01 00:00:00 UTC 作为时间历元。
      • 注释 这和 UNIX 时间 以及POSIX.1 的基本概念实质相同。这是 ISO C++11 中不存在但 ISO C++20 起在添加的要求,由 WG21 P0355R7 引入。
      • 原理 主要的标准库实现( libstdc++ 、libc++ 或 Microsoft STL )都满足这个要求。被支持的实现不使用其它标准库,因此也都满足这个要求。即便使用其它实现,未来出现不满足这个要求的实现的可能性很小。

原理 以上假定通常被主流实现支持,但并不直接被满足基准实现要求中的 ISO C++ 保证,因此不作为基准实现要求。但是,被支持的实现平台一般都满足这些条件,因此直接完整地使用本项目的应用可以直接依赖这些特性。区分基准实现要求和可选实现要求允许对本项目的组件以更细粒度的方式使用。

历史实现要求

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

  • 假定提供撤销标准库未定义行为的保证:
    • 撤销 [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 建议的特性检查判断)。

扩展特性

  除非另行指定,不依赖实现的方言扩展。

注释 在确保实现能支持时,在特定的代码中可通过条件包含等方式选用。

  关于语言特性中的具体使用以及启用的扩展,参见 YSLib 项目文档 doc/LanguageRules.txtdoc/YBase.txt 等具体部分的相关开发文档。

  若实现默认具有不符合标准的特性,在本项目的代码中不依赖这些特性,即便外部依赖项可能对此进行配置(如 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 项目组织为一个逻辑上的树形结构,其叶节点称为模块。类似文件系统,非叶节点称为模块目录。在源代码中使用模块路径来标识不同的模块,其分隔符为 :: 。在 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 在宿主实现上都提供了更加丰富的功能。

持久数据

  除非另行指定,项目中处理的文件符合本节规则。

文件格式

  文件可具有编码的数据。存储库文件的内容作为持久保存的数据,其中的编码可满足外部交互的需要而应约定具体的格式,即外部编码

  存储库的文件编码应当使用外部编码。

原理 存储库的文件具有持久保存的内容。明确格式以确保内容的相对稳定,有助于维护。

  若本 wiki 的规则(en-US) 存在指定文件格式的规则,在适用外部编码的上下文中,从其约定。

原理 应用 wiki 规则以允许本 wiki 作为版本库的一部分被管理。

文本文件

推论wiki 规则,文本文件使用带有字节序标记(BOM, byte order mark) 的 UTF-8 编码以及 CR+LF 行尾(EOL, end-of-line) 。

原理 文本文件格式具有以下几个原因:

  • 使用 UTF-8 + BOM 的原因是:
    • 作为外部编码具有良好的可用性和可移植性。
      • UTF-8 是当前最被广泛使用的外部文本编码。
      • 尽管具有技术缺陷,普遍上仍然缺乏其它替代。
      • 尽管存在历史遗留问题问题导致的误解,因为确定使用的编码,相对不使用 BOM ,处理 BOM 的程序的行为更加确定,而具有更好的可移植性。
        • 不带 BOM 的 UTF-8 编码的文本内容可能在不同的运行环境中识别为不同的编码。
          • 例如,在 Windows 程序(例如 Visual Studio )中带有 BOM 的文本文件不会认为使用代码页(codepage) 的非 UTF 编码的文本文件;在 Linux 等环境的用户程序中往往默认不带 BOM 的文本文件以 UTF-8 编码。
    • 可兼容不同的 UTF 编码作为外部编码(外部编码 UTF 的主要使用场景);参见 wiki 规则中的原理(rationale) 。
    • 同以上应用 wiki 规则的原理,使文本文件能作为版本库的一部分管理。
  • 使用 CR+LF 的原因是:
    • CR 和 LF 具有不同的含义。强调 CR+LF 允许处理只有 CR 的例外情形(尽管除直接以 CR 作为行尾的情形这通常被视为格式错误)。
    • CR+LF 被强调作为外部编码。相对地,ISO C 和 ISO C++ 等文件流读写实现的文本模式可能处理这些格式并转换为只有 LF 结尾的内部编码。直接使用 LF 可以被兼容且更简单,但损失了边界区分外部编码和内部编码的边界。
    • CR+LF 是一些最被广泛使用的外部编码标准(如 HTTP )中要求的一部分。

  程序的源代码作为文本文件,应满足文本文件的要求。

注释 静态确定的源代码一般都以文本文件的形式存储。

代码规范

  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

Shell 语言使用规范

  本节提供除测试用途和一次性交互式使用场景外的 shell 命令语言及其兼容方言的代码的特性使用和代码风格的规则。

注释 YSLib 提供和使用的shell 脚本具有明确的环境要求,其中的代码也被本节涵盖且满足其它一些具体要求

空白符

  注意 shell 语言和方言的类似赋值的操作符(如 =:= 等)和操作数不能有空白符。

原理

  文法规则中,赋值不能有空白符,否则被视为单独的构成命令的字(word) 而被分析为命令或参数等非预期的记号。具体地,没有空白符的带有赋值的字视为 ASSIGNMENT_WORD 在之后进一步被解析。

  这和历史同期的 B 语言和 C 语言不同,因为 shell 语言被设计为命令语言,而非更通用的编程语言更强调表达式(例如,考虑到赋值作为表达式可以嵌套使用作为另一赋值表达式的右操作数),赋值被直接视为一类表达式而非一类记号。

重定向

  一般地,shell 重定向语法中:

  • >&<& 按 shell 语言文法规则要求被视为一个记号时内部不应有空白符。
  • 重定向操作符和以数值表示文件描述符或非文件名的 - 操作数之间一般不应有空白符,而其它操作数之间保留空格(与常规命令以及命令参数类似)。
  • 省略重定向操作符之前表示文件描述符的操作数 1

  重定向在命令中出现的位置未指定,可被适当调整位置以满足源代码排版清晰,如减少出现在字符串参数中的 \

注释 这可能引起 ShellCheck 警告 SC2210 。这是其中例外情形而可安全忽略。

示例

  • 使用 > &2> & 2 复制输出文件描述符 2 是错误的。
  • 一般应使用 2> /dev/null2> 1 ,而不是 2>/dev/null2>1
  • 一般应使用 >&2>&- 而不是 1>&21 >& 2>& -

原理

  作为命令语言,shell 语言传统上更加强调重定向的 I/O 功能先于指定环境的赋值语法。一般的实现中,重定向被分析出记号之前单独处理,重定向操作符和操作数之间的空格是可选的。POSIX 关于重定向的规范也有和命令中的其它部分更多非平凡的规则:

  • 在 shell 重定向的一般格式 [n]redir_op word 这个表示中,重定向操作符和之后的操作数技术上没有空格,仅为元语言避免 redir_opword 保持。
    • 后文 [n]>word 等描述和示例不再有空格也可以证实这一点。
  • 依照 shell 文法规则
    • 重定向操作符之前的操作数是 IO_NUMBER 而非 WORD
    • 重定向操作符之后的操作数在是 io_file ,由重定向操作符(如记号 > )和规约至 filenameWORD 组成。

  但是,除了 >& 应被视为整个记号这样的符合文法规则要求的要点外,这里约定的风格不直接由文法规则决定,而指派相对独立的用例场景分类。(事实上,套用文法规约反而会和这里规则相反地得到 IO_NUMBER 和之后的重定向操作符之间有空白符而重定向操作符和之后的字之间可省略空白符的结论。)这是因为:

  • 强调重定向的传统惯用的命令风格中,文件描述符或非文件名的 - 操作数和重定向操作符被视为一个整体,而命令中的其它字则不是,如:command < input > output 2>&1
    • 重定向规则中的 - 在被(不视为文件名)单独支持时,和文件描述符遵循相同的规则。
    • 和重定向的格式描述中的示例不同,在 exec 的示例等描述中,使用了这种方式,同时在格式上也把 - 视为文件描述符。
  • 操作数是否被视为文件描述符的规则和具体重定向操作符相关,特别是预期使用文件名的而实际是文件名的易错情形。强调区分这两种用法风格可能减少这些误用。

保留字和命令

  注意命令之间需要有空白符;但 (( 虽然是命令,其后的表达式被视为特设语法的一部分而不被视为命令的操作数而可能被特殊处理。具体地:

  • [ 和参数之间应有空白符,否则语法错误。
  • $(((( 和之后的表达式可以有空白符,但一般应省略。
    • 原理 这使 iffor 等关键字相对接近 C 语法风格惯例。
  • (( 和之前的关键字之间不省略空格。
    • 注释 尽管在 GNU Bash 文档中没有提及,如 if((for(( 实际上可被 Bash 解释器识别并拆分出关键字和 (( 记号。这不被这里依赖。
    • 原理 一般避免依赖 POSIX 从命令识别保留字以外的附加处理。
  • 注释 (( 在扩展方言中使用。POSIX 在组合命令(compound commands) 中提及实现可能提供这种算术求值(arithmetic evaluation) 语法。

  除非另行指定,以下命令的行为未指定:

  • 在 POSIX 或使用的 shell 语言实现环境(如特定方言扩展)中未指定的行为。
  • 诊断消息的具体内容。
  • POSIX 指定的命令行长度限制以及显式引用这个限制的情形,这个命令行长度限制的具体值。

引号

  使用引用时,优先使用单引号。

  考察以下情形决定是否使用引号:

  • 在字符串字面量使用 shell 参数或其它其它在字面量中起作用的扩展时,应使用双引号。
  • 在字符串字面量使用 shell 通配符或其它不在字面量中起作用的扩展时,应不使用双引号。
  • echo 和类似能支持多个参数合并字符串的命令的字符串字面量一般应始终使用引号以确保作为单一参数处理。
  • 对其它的简单的命令参数,特别是相对固定的参数(如 %s 这样的仅包含不被 shell 扩展的格式字符串),一般应省略引号。

原理 使用引号使字符串字面量的内容和源代码直接对应,可避免一些非预期 shell 导致的难以检查的误用。

变量和环境

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

  • 若没有指定变量的初始化调用函数,则使用 shell 内建的方式(如赋值)直接实现。
    • 注释 变量的初始化还可通过调用函数间接实现。
  • 除非另行指定,初始化的变量具有的只读或导出等属性未指定。
    • 注释 之后可能通过声明只读或取消属性。
  • 除非另行指定,变量被修改前要求非只读。
    • 若不满足要求,则运行出错。
    • 注释 另行指定的特定的变量可能通过声明取消只读之后被修改。
    • 取消只读的变量在修改后重新设置只读属性。
  • 初始化变量时若需使用默认值,可能发生附加的求值(如命令调用)以其结果确定具体默认值。
  • 除非变量的初始化被指定为由特定的函数确定,变量在未指定的脚本被执行时无条件初始化,而不需要使用变量的值前调用特定的函数。
  • 除非另行指定:
    • 在特定函数中初始化的变量由此函数指定其默认值。
    • 初始化时使用的外部环境变量的值可假定和脚本运行环境直接或间接调用脚本前相同。

  除非另行指定,shell 的变量可能被 shell 脚本执行时修改。

原理 这允许一般不要求检查变量是否只读,包括提供默认值或直接复用外部可能已初始化的环境变量,以简化实现。

  注意循环等语法隐式引入的 shell 变量是全局变量。若有可能(当 shell 方言支持的上下文时),显式声明局部变量后再使用变量;在 POSIX shell 中可引入函数使用参数代替。

原理 使用局部变量代替全局变量以在不需要共享时避免污染全局环境。这要求方言和特定的上下文(如函数体中),不总是容易实现。使用函数是相对容易在 POSIX shell 中实现局部作用于的方法;其它方式(如子 shell )可具有更大的开销和更多的可能非预期的副作用(如无法共享变量),因此不作要求。

实用程序

  实用程序(utility) 提供 shell 语言中可用的命令。

  • 功能可满足需要且没有显著的性能问题时,优先使用兼容 POSIX 的实用程序。
  • 除非另行指定,假定实用程序不具有调用时不一致的行为的别名。
    • 注释 不假定不存在对其它命令可见的不同的行为,如特定的 aliascommand -v 调用可能结果不同。
    • 注释 约定的未指定行为不视为不一致,如命令行长度的改变。
    • 示例 在非交互式 shell 中,alias=ls --color=auto 不影响 ls 命令的行为而满足假定。
  • 使用 POSIX env 实用程序 以避免对只读变量的赋值。
  • 除非另行指定,不使用 which
    • 原理 这个命令不是 POSIX 实用程序,兼容性相对较差,且一般没有 shell 内建支持。
    • 在 GNU Bash 中,对检查一个操作数的情形,使用 type -P 代替。
      • 在 makefile 中,可使用 SHELLbash -c 等方式调用 type -P
    • 在不依赖 shell 扩展和不同行为时,可使用 command -v
      • 注意 command -v 对别名等非 $PATH 中可搜索的程序以及多个参数时的处理和 which 的一般实现不同。
    • 在没有满足要求的替代确定可依赖 which 时,可使用 which

构建选项

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

脚本

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

  脚本一般通过调用解释器运行。脚本运行环境(script runtime environment) 是支持脚本运行的命令行程序运行环境。除非另行指定,脚本运行环境是宿主环境或非宿主环境内部通过平台模拟提供的类宿主环境。

注释 符合 YSLib 源码 YF_Hosted 定义支持的平台能提供宿主环境。

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

外部环境

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

  项目中提供的脚本在版本库中具有固定的相对路径。除解释环境适用的公共的约定,脚本不预设绝对路径的假定。

示例 公共约定如使用一般类 UNIX 系统使用的文件系统布局的 FHS(文件系统层次结构标准)。另见文件系统布局

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

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

脚本文件

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

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

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

  NPLA1 脚本约定使用带有 BOM 的 UTF-8 编码(去除 BOM 的脚本可能兼容 ASCII )。

注释

  关于 NPLA1 脚本编码,另见:

接口约定

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

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

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

路径

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

  特定的路径上的规约操作是路径解析(path resolution)

  路径的字符串表示可具有分隔符(separater) ,以显式区分组件的边界。

  路径后缀(path suffix) 是路径中最后的一个或多个组件序列构成的序列。路径表示的后缀蕴含分隔这些组件的内部分隔符和之后的分隔符。除非另行指定,一个路径的后缀中的组件序列是最后一个组件(若存在)。

  路径前缀(path prefix) 是路径或其表示中不包含路径后缀的部分。

  在支持相对路径的环境中,是否支持相对路径取决于被使用的具体程序(可能是脚本)。

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

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

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

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

示例

  在使用 POSIX.1 定义的宿主环境中:

注释

  POSIX 路径名 / 是根路径;前缀 // 在路径解析中具有实现定义的行为,但 3 个或以上 / 视为 1 个 / 而不具有类似的实现定义语义。

  Windows 的 POSIX 实现如 Cygwin 和 MSYS 使用 // 作为 POSIX 路径的 UNC 路径前缀(在 Win32 中为 \\ )。

环境变量

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

  程序中参照环境变量管理的具有对应字符串类型的值的具名配置项,也可视为具有默认值的变量。

注释 被作为环境变量传递的值应能通过互操作确保和环境变量的值具有对应关系。即便变量仅在脚本或非脚本的程序内部使用,也可用本节的规则,但这部分是可选的。

  变量是否使用默认值总在先于第一次使用变量的值的初始化时确定。指定按需按需(as needed) 初始化的变量,当且仅当具有非空值时被初始化为默认值。

  YSLib 程序可按需使用以下能通过外部环境指定值的环境变量及其默认值:

  • 用于指定命令:
    • SHBuild :外部 SHBuild 可执行文件路径。
  • 用于指定存储库中的资源:
    • SHBuild_ToolDir :工具目录中的脚本目录即 Tools/Scripts
    • SHBuild_BaseDir : SHBuild 目录,是工具目录中提供 SHBuild 的源代码的目录。
      • 默认值按 SHBuild_ToolDir 初始化后的相对位置确定,为 "$SHBuild_ToolDir/../SHBuild"
    • YSLib_BaseDir :YSLib 目录,即版本库检出后的工作目录。
  • 用于指定输出目录:
    • 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 根路径。
      • SHBuild 构建环境中,指定 stage 2 SHBuild 的输出目录;默认值是 "$YSLib_BaseDir/sysroot"
      • 部署后环境中,指定一个先前的 Sysroot 安装位置;默认值按 SHBuild 初始化后的相对位置确定。
        • 注释 当前仅在经过 SHBuild 构建环境后直接复用,而不单独作为入口位置
      • 其值被作为路径使用时,结尾的一个或多个 / 被忽略。
      • 原理 忽略结尾 / 允许 Sysroot 根路径的字符串表示作为路径前缀和一个 POSIX 绝对路径的路径前缀(以 / 起始的字符串)拼接时,把后者视为相对 Sysroot 根路径的路径后缀而不在结果中引入多余的 / 。否则,支持被拼接的路径前缀视为路径后缀时 Sysroot 根路径无法支持指定 POSIX 根路径 / ,因为这要求变量 SHBuild_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 的帮助信息。
    • 其它构建脚本使用的变量:详见以下具体脚本的说明。
  • 注释 上述变量中,可作为入口位置的变量的默认值在此省略,详见文件定位的相关说明。

注释 脚本中的环境配置约定指定更具体的脚本可访问的变量,其命名满足以上变量名要求。

  若以上变量在上述任一情形存在作为路径的默认值且外部环境指定变量的值,则指定的值应表示合法的路径。若指定的是可执行文件,则具有可访问和执行的权限。脚本可对这些条件进行检查,若失败则引起错误。

互操作

  脚本源代码中可引用自身或其它程序,被引用的程序是目标程序(target program) 。目标程序可使用脚本实现,即目标脚本(target script) 。被执行的当前脚本(current script) 可能通过文件路径和脚本语言支持的构造使用目标程序。

示例 Shell 脚本中可使用 .source 命令执行参数指定路径的兼容 shell 的目标脚本、调用脚本解释器命令执行参数指定的被解释器支持的目标脚本或直接使用带有适当权限的文件名作为命令执行目标程序。

文件定位

  被引用的目标程序或其它文件被运行时相对特定的文件系统中的位置应被确定,即对目标文件(target file)定位(locating)

  定位包含一次或多次对输入的指定文件位置的参数进行处理,输出新的路径作为候选,这个过程即解析(resolution)入口位置(entry location) 是定位的起始位置,作为第一步路径解析的输入,之后可选的步骤迭代可继续输入不同的相对路径迭代解析,直至取得与其的目标文件的位置。入口位置可以是当前被执行的脚本文件在文件系统中的位置,或通过特定的环境变量的值指定。其它一些变量也可指定路径解析步骤的输入。

注释 定位解析的迭代方式和文件系统对路径解析的方式类似,但输入和输出不一定是路径。通常最终的输出是能在当前上下文中直接和唯一地确定文件位置的路径。若当前上下文不支持访问工作目录,这个路径一般应为绝对路径。和路径解析不同,位置的抽象解析可以包含执行任意操作而不排除其中可能具有副作用,但通常仍应完全避免依赖这些副作用。

  同时在多个环境都适用的目标文件,相对当前脚本的位置不保证相同。若表达这些相对位置的相对路径确保作用等价,则视为相对位置唯一确定。

注释 目标程序通常是 YSLib 提供的脚本或构建后部署的程序。若使用其它程序,可能安装在 $PATH 可引用的路径或其它周知的位置,而不需要使用本节的方式定位;但本节的方式仍可能(有限地)适用。

  当前设计中,YSLib 提供的脚本可以在 SHBuild 构建环境部署后环境中使用,即可访问和可执行。Sysroot 布局可提供在部署后环境脚本的特定位置的目标文件。YSLib 存储库提供部署之前的确定的相对位置的脚本。

注释 可能有程序在不同环境中的可观察行为不完全相同,如 stage 1 和 stage 2 SHBuild 依赖不同的库,也不保证未来具有完全相同的特性集。

  定位解析的典型情形如下:

  • 使用已知位置的相对路径定位:位置由表示已知位置的路径和相对当前脚本的相对路径确定。
    • 其中,已知位置是当前脚本或一个已被定位的目标文件的位置。
    • 注释 当脚本同时支持 SHBuild 构建环境和部署后环境,预期使用等效的目标文件,一般应使用文件直接确定而非以下可配置的环境变量。对无法唯一定位的目标文件,则可使用以下其它方式。
  • 使用变量的值定位:位置由作为路径的变量的值指定,若非空则需要默认值
  • 搜索路径定位:使用搜索特定变量值中的元素作为路径前缀的结果。

  可用于指定入口的环境变量的默认值和特定脚本被设计适应的部署环境可能相关,确定如下:

  • SHBuild
    • SHBuild 构建环境中的安装过程中的默认值参见构建过程
    • 否则,对部署后环境中的可执行脚本,默认值是 Sysroot 中的脚本直接指定 Sysroot 根目录定位的结果。
    • 否则,默认值是搜索路径定位的结果。
  • SHBuild_ToolDir
    • 默认值按脚本根据存储库所在目录确定。
    • 注释 在 SHBuild 构建之后,脚本可能脱离存储库运行,不保证这个目录存在,因此部署后环境下的脚本不依赖 SHBuild_ToolDir 取脚本路径。
  • YSLib_BaseDir
    • 默认值按变量 SHBuild_ToolDir 初始化后的值指定的位置的相对路径确定,为 "$SHBuild_ToolDir/../.."
  • 原理 工具目录及其子目录相对存储库的布局(相对路径)是确定的,可直接使用相对路径定位。
  • 原理 Sysroot 布局已能在 stage 1 环境之后假定固定的相对路径的存在性。为简化规则,不使用变量 SHBuild_SysRoot 的值指定入口。

  在以上通用的变量外,特定脚本使用不同的变量指定入口位置,默认值由实现定义(如通过脚本的接口规范指定)或未指定。

  指定入口位置以外,定位解析的中间结果可被保存在由实现定义或未指定的变量中。

注释 使用的变量一般被明确指定或至少指定变量名的模式(pattern) ,以便用户避免使用可能引发潜在冲突的无关的变量。

  YSLib 提供的脚本的实现中,定位目标文件所在目录(以确定脚本所在路径)的一般策略如下:

  • 若目标文件在 SHBuild 构建环境和部署后环境中同时可用,且被当前脚本使用具有的可观察行为相同,同时相对当前脚本的位置唯一确定,使用当前脚本作为已知位置,以相对路径定位。
    • 原理 总是使用当前脚本的相对路径定位,因为需要兼容 SHBuild 构建环境,而部署后环境可用的脚本无法依赖变量 SHBuild_ToolDir 的值指定目标文件所在的目录。
  • 否则,若目标文件是部署后环境中被部署的公开可执行程序,使用变量的值指定,其默认值对应指定其位置,是搜索路径定位的结果。
    • 若目标文件是可执行程序 SHBuild ,使用变量 SHBuild
    • 否则,使用由实现定义的变量。
    • 注释SHBuild 外仅可在部署后环境使用;确定默认值的方式和部署后环境的 SHBuild 相同。
  • 否则,若目标文件是 Sysroot 根路径指定的目录,使用 SHBuild 的位置的相对路径定位。
  • 否则,若目标文件在 Sysroot 根路径指定的目录或子目录中,按 Sysroot 根目录的位置的相对路径定位目标文件。
  • 否则,若目标文件是工具目录中的脚本目录,使用变量 SHBuild_ToolDir 的值作为定位的结果。
  • 否则,若目标文件在工具目录中的脚本目录中,按工具目录的位置的相对路径定位目标文件。
  • 否则,若目标文件是存储库根目录,使用变量 YSLib_BaseDir 的值作为定位的结果。
  • 否则,若目标文件在存储库根目录或子目录中,按存储库根目录的位置的相对路径定位目标文件。
    • 注释 典型地,YSLib 存储库的工具目录外的源代码属于这种情形。
  • 否则,对每个目标文件,当前脚本可通过由实现定义的方式确定其位置的方式。
    • 注释 这是可选的。可能不通过公开文档指定,如使用注释。
  • 否则,目标文件的位置未指定。
    • 注释 一般仅在实现内部使用未指定唯一位置的文件,以避免影响约定的脚本程序的可观察行为。
  • 注释 一般分别使用以上策略对不同目标文件定位。
    • 原理 即便脚本还依赖以下其它规则定位仅在部署后环境可用的目标文件,也不影响单独定位,以在支持尽可能多的配置组合同时减少规则的复杂性。
    • 示例 在 SHBuild 构建环境和部署后环境可用的公共脚本,不使用 SHBuild 以外的变量的值指定位置。
  • 注释 通过变量定位时,原则上优先使用相对路径较简单的较近父目录的位置。
    • 原理 单独指定更上级目录的变量因此无效。这避免要求实现检查所有可能涉及的变量。
  • 注释 一些目标文件的定位可作为其它文件的已完成的解析步骤。
    • 示例 通过定位确定工具目录位置后,可定位其中的其它文件。
    • 这些定位不一定通过周知的变量访问,可能由实现定义或未指定。

  定位所在的目录后,其中的脚本可基于所在目录的路径表达式构造路径指定位置。定位可在脚本中的不同位置中出现,应当满足依赖项的使用需求。

原理 当存在多个配置入口的不同环境变量时,以上定位的规则能无歧义地指定其中默认值的生效方式。特别地,Sysroot 的布局蕴含 SHBuild 、Sysroot 根目录和目标文件的位置之间的相对路径是确定的,能保证唯一确定路径。

警告 假定脚本定位的目标程序是支持的版本且不依赖其具体位置,否则调用目标程序的脚本行为未指定。特别地,混用不同版本的存储库或 Sysroot 对应的文件不被支持。

Shell 脚本

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

#!/usr/bin/bash

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

#!/usr/bin/env bash

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

#!/usr/bin/sh

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

#!/usr/bin/env sh

  若脚本可以确保兼容 POSIX shell ,使用 sh 而不是 bash

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

  • 使用满足要求的 shell 语言实现:
    • 对 Bash 脚本:
      • 满足版本要求:当前最低版本为 GNU 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

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

  若对应解释器位于其它目录,可通过符号链接以满足以上要求。

注释 一般不检查解释器的路径是否指定一个符号链接。

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

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

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

  脚本可能执行 set -e 以及早发现错误,包括但不限于变量的默认值初始化失败。

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

Shell 语言使用规约

  对 shell 命令语言和兼容方言的使用符合 shell 语言使用规范

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

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

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

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

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

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

Shell 变量

  除非另行指定,shell 脚本对变量的使用符合 shell 代码对变量的规则

  Shell 脚本可使用环境变量指定外部配置变量的值,即外部变量(external variable)

示例 Bash 命令 _="${SHBuild:="$(type -P SHBuild)"}" 初始化变量 SHBuild 的值实现搜索 $PATH 的路径定位 SHBuild 命令。

  Shell 的内部变量(internal variable) 在脚本之间使用,通过 .source 命令共享

注释 外部变量和内部变量类似 C 语言标识符的链接。类似地,局部变量不是外部变量或内部变量。

  以下内部变量具有公共的含义:

  • INC_SHBuild_ :用于指定标记已被检查.source 命令包含的前缀。
    • 注释 类似 C 和 C++ 源程序中的守卫宏(macro name) 的名称。多次调用可能效果不同的脚本一般不使用。
    • 这些变量不被设置为只读以支持显式地取消定义而重复包含脚本。

  除非另行指定,外部变量总是允许指定通过环境变量值,内部变量总是能通过互操作共享值,但这些变量不一定被视为公开接口。

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

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

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

Shell 函数

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

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

文件定位的实现

  定位入口位置的实现一般如下:

  • 使用当前脚本的相对路径定位:
    • 示例 Bash 脚本可使用 ${BASH_SOURCE[0]} 引用自身,使用 $(dirname ${BASH_SOURCE[0]}) 表达被定位的目录,如:. "$(dirname "${BASH_SOURCE[0]}")/SHBuild-common.sh" 执行当前脚本目录下的 SHBuild-common.sh
    • 示例 Shell 脚本可使用 "$0" 指定自身位置,使用 cddirnamepwd 解析构造的相对路径的结果。
  • 搜索路径定位:
    • 示例 Bash 脚本可使用 $(type -P program) 指定 program 的位置。

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 。