GYP(node-gyp)基于.gyp文件构建C/C++插件,除文件结构外,.gyp还应遵守一定的语法规则。
- 原始类型
- 总体结构
- 合并
- 路径名相关
- 单例列表项
- 包含其他文件
- 变量与条件
- 条件语句
- 变量扩展的时机及条件
Dependencies和依赖- 链接依赖关系
- 加载文件以解决依赖性
- 构建配置
- 列表过滤器
- 排除列表(
!) - 模式列表(
/) - 查找排除项
- 处理排序
- 设置键
1. 原始类型
在.gyp文件中,包含了以下几种输入类型:
字符串值(String values),使用'单引号'或"双引号"将值括起来。按照惯例,使用单引号。整型值(Integer values),用十进制表示,没有任何修饰符。整数在输入文件中很少见,但在布尔值较常用,按惯例用1表示真值,用0表示假。列表(Lists),用方括号([和])括其来表示,其中各项值用(,)分隔。列表可能包含任何其他基本类型,包括其他列表。一般来说,列表中的每个项目必须与列表中的所有其他项目具有相同的类型,但是在有些情况下(如在conditions内部),列表结构更加严格。列表尾随逗号是允许的。如,以下是一个包含三个字符串值的列表:
[ 'Generate', 'Your', 'Projects', ]
字典(Dictionaries),键-值对的映射。所有的键都是字符串;值可以是任何其他基本类型,包括其他字典。 一个字典用大括号({和})括起来。键在值之前,用冒号(:)分隔。字典中各键值对之间用逗号(:)分隔。允许尾随逗号。以下是一个包含三种类型值的列表:
{ 'inputs': ['version.c.in'], 'outputs': ['version.c'], 'process_outputs_as_sources': 1, }
2. 总体结构
GYP 输入文件被组织为结构化数据。在每个.gyp或.gypi(包含)文件的根作用域上是一个字典。该字典中的键和值以,及值中包含的所有子节点提供了文件中包含的数据。通过一定的方式解释键名称及其相关的值来给出这个数据的含义(参见设置键)。
2.1 注释(#)
在输入文件中,可以通过井号(#)添加注释。井号后的任何文字,直到行尾,都会被视为注释。
注释示例:
{
'school_supplies': [
'Marble composition book',
'Sharp #2 pencil',
'Safety scissors', # You still shouldn't run with these
],
}
在上面示例中,'Sharp #2 pencil'不会被视为注释,而# You still shouldn't run with these会被认为是注释。
3. 合并
3.1 合并基础(=、?、+)
GYP输入文件的许多操作都是通过将字典和列表项合并在一起实现的。在合并操作中,重要的是要识别源和目标值之间的区别。其中,源值中的项目被合并到目标中,这将使源保持不变,而目标值会被修改。在合并时,一个字典只能被合并到另一个字典中,而一个列表只能被合并到另一个列表中。
在合并字典时,源中每个键:
- 如果键在目标字典中不存在,则将其插入并直接复制其值。
- 如果键已经存在:
- 如果其值是字典,则使用源和目标值字典执行字典合并过程
- 如果其值是列表,则使用源和目标值列表执行列表合并过程
- 如果其值是字符串或整数,则目标值将由源值替换
合并列表时,如果列表是字典中的值,则根据附加到键名称的后缀进行合并:
- 如果键以等号(
=)结尾,那么源列表会完全替换目标列表 - 如果键以问号(
?)结尾,则只有当键不在目标时,才会将源列表设置为目标列表。(?为条件匹配符) - 如果键以加号(
+)结尾,那么源列表会被追加目标列表 - 如果键没有修饰符,那么源列表内容附加到目标列表(默认)
示例:
源字典如下:
{
'include_dirs+': [
'shared_stuff/public',
],
'link_settings': {
'libraries': [
'-lshared_stuff',
],
},
'test': 1,
}
目标字典如下:
{
'target_name': 'hello',
'sources': [
'kitty.cc',
],
'include_dirs': [
'headers',
],
'link_settings': {
'libraries': [
'-lm',
],
'library_dirs': [
'/usr/lib',
],
},
'test': 0,
}
合并后:
{
'target_name': 'hello',
'sources': [
'kitty.cc',
],
'include_dirs': [
'shared_stuff/public', # Merged, list item prepended due to include_dirs+
'headers',
],
'link_settings': {
'libraries': [
'-lm',
'-lshared_stuff', # Merged, list item appended
],
'library_dirs': [
'/usr/lib',
],
},
'test': 1, # Merged, int value replaced
}
4. 路径名相关
在.gyp或.gypi文件中,很多字符串值会被视为相对于定义它们的文件的路径名。
与以下键名相关联的字符串值,或包含在与以下键关联的列表中的字符串值被视为路径名:
- destination
- files
- include_dirs
- inputs
- libraries
- outputs
- sources
- mac_bundle_resources
- mac_framework_dirs
- msvs_cygwin_dirs
- msvs_props
另外,如果键以下面后缀结尾,且值为字符串或列表(列表中为字符串值)将视为路径名:
_dir_dirs_file_files_path_paths
但是,当字符串中包含以下任何字符串值,都不会被认为是相对化路径:
/绝对路径$引用构建系统变量扩展-支持指定的项目。如:-llib表示“库lib在指定路径内查找库<、 >与!为 GYP 扩展
示例
源字典../build/common.gypi:
{
'include_dirs': ['include'], # 会认为是相对路径 ../build
'libraries': ['-lz'], # - 开头,不会被认为是路径
'defines': ['NDEBUG'], # defines 不包含路径名
}
目标字典base.gyp:
{
'sources': ['string_util.cc'],
}
合并后的字典:
{
'sources': ['string_util.cc'],
'include_dirs': ['../build/include'],
'libraries': ['-lz'],
'defines': ['NDEBUG'],
}
5. 单例列表项
有些列表项被视为单例,列表合并过程将在合并时执行特殊的规则。目前,列表中任何不以短线(-)开头的字符串项都会被视为单例。将单例附加或列入列表时,如果该列表已经在列表中,则只有较早的实例会被保留在合并列表中。
示例
源字典:
{
'defines': [
'EXPERIMENT=1',
'NDEBUG',
],
}
目标字典:
{
'defines': [
'NDEBUG',
'USE_THREADS',
],
}
合并后的字典:
{
'defines': [
'NDEBUG',
'USE_THREADS',
'EXPERIMENT=1', # 注意,NDEBUG 不是被追加进去的
],
}
6. 包含其他文件
如果使用-I(--include)参数来调用GYP,则指定的任何文件都将隐式地合并到所有.gyp文件的根字典中。
includes节点可以放在.gyp或.gypi文件中的任何位置。includes包含其他文件列表的部分时,会按顺序处理,并在找到includes部分的位置合并到最终字典中。在.gyp文件根目录中includes部分在命令行中任何包含-I选项后进行合并。
includes节点在加载文件之后立即处理段,甚至在变量和条件处理之前,所以不会包括基于变量引用的文件。
但是,includes部分放置在conditional部分内。包含的文件本身将被加载,但是如果相关的条件不正确,它的字典会被丢弃。
7. 变量与条件
7.1 变量
GYP 内部主要有以下三类变量:
预定义的变量- 按照惯例,使用CAPITAL_LETTERS命名规则。预定义的变量由GYP自动设置,也能能被忽略(不建议)。用户定义的变量- 所有字典中,都有一个名为variables的键,其中包含变量名(键)及其内容(值)之间的映射,可以是字符串、整数或字符串列表自动变量- 所有字典中,任何具有字符串值的键都有一个相应的自动变量,其名称与带有下划线(_)前缀的键名相同。
7.2 默认值变量(%)
在variables节点中,用百分号(%)后缀命名的键表示只有在处理时未定义变量时才应设置该变量。这可以用来为变量提供默认值,否则这些变量是未定义的,这样可以可靠地在变量扩展或条件处理中使用它们。
7.3 预定义变量
每个GYP生成器模块会为以下变量提供默认值:
OS:输出的操作系统的名称。常见值有:'linux''mac''win'
EXECUTABLE_PREFIX:前缀,(如果有)将应用于可执行文件名称。EXECUTABLE_SUFFIX:后缀(如果有)应用于可执行文件名称。 默认是一个空字符串。在Windows上,会是.exe,在其他地方,默认是一个空字符串。INTERMEDIATE_DIR:用于放置中间构建结果的目录。INTERMEDIATE_DIR只能保证在单个目标(targes)中可访问。这个变量在规则(rules)和动作(actions)上下文中是最有用的。PRODUCT_DIR:放置每个目标targes的主要输出(例如可执行文件和库)的目录RULE_INPUT_ROOT:输入文件的基本名称(如'foo')RULE_INPUT_EXT:输入文件的扩展名(如'.cc')RULE_INPUT_NAME:输入文件的全名(如'foo.cc')RULE_INPUT_PATH:输入文件路径(如'/bar/foo.cc')SHARED_INTERMEDIATE_DIR:用于放置中间构建结果的目录,并使其可供其他目标访问。与INTERMEDIATE_DIR不同,项目中的每个目标(可能跨越多个.gyp文件)共享相同的SHARED_INTERMEDIATE_DIR。
在某些情况下,以下附加的预定义变量可能是可用的:
DEPTH:当使用--depth参数调用GYP时,在处理任何.gyp文件时,DEPTH将是从.gyp文件到由--depth参数指定的目录的相对路径。
7.4 用户定义变量
用户定义变量可以用其他变量来定义,但不能是在同一范围内定义的其他变量。
7.5 变量扩展(<、>、<@、>@)
GYP提供了两种形式的变量扩展,"early"或"pre")扩展,以及"late","post"或"target"扩展。 它们具有相似的语法,仅在用于引用它们的字符方面有所不同。
- 早期(Early)扩展由小于(
<)字符引入的 - 后期(Late)扩展由大于(
>)字符引入的
参见早期和晚期阶段
这些字符的选择是基于与构建系统本身使用的变量格式不冲突的要求。虽然美元符号($)是最适合变量扩展的,但是其已经被使用了,因为大多数构建系统已经将这个字符用于它们自己的可变扩展。使用不同的字符意味着不需要转义机制来区分GYP变量和构建系统变量,而将构建系统变量写入GYP文件并不麻烦。
变量可能包含列表或字符串,变量扩展可能出现在列表或字符串上下文中。 有变量扩展的变体形式,可以用来确定每种情况下如何扩展每种类型的变量。
- 当变量通过
<(VAR)或>(VAR)形式引入时- 如果
VAR是字符串,则字符串中的变量引用将被变量的字符串值替换。 - 如果
VAR是列表,则字符串中的变量引用被一个包含所有变量列表项的串联的字符串替换。通常情况下,这些项目之间会有空格,但具体行为是针对特定于生成器的。所有生成器使用的精确编码应该是允许每个列表项作为一个单独的参数作为系统上的程序参数使用,这个参数由生成器产生输出的。
- 如果
- 当变量通过
<@(VAR)或>@(VAR)形式引入时- 列表项目必须是
<@(VAR)或>@(VAR) - 如果
VAR是列表,则将其每个元素插入到正在进行扩展的列表中,替换包含变量引用的列表项。 - 如果
VAR是字符串,该字符串被转换为一个列表,插入到正在扩展的列表中。列表中的转换是特定于发生器的,通常,字符串中的空格将作为列表项之间的分隔符。将字符串转换为列表的具体方法应该是用于在上面的字符串上下文中扩展列表变量的编码方法的反转。
- 列表项目必须是
7.6 命令扩展(!<、!<@)
命令扩展与变量扩展功能类似,但不是解析变量引用,而是使GYP在生成时执行命令,并使用命令的输出作为替换。命令扩展是由小于和感叹号(code>!<)引入的。<
在命令扩展中,括号中包含的整个字符串被传递给系统的shell。命令的输出被分配给一个字符串值,如果使用了@字符,那么这个字符串的值可以在列表上下文中进行扩展,就像变量扩展一样。
另外,命令扩展(与其他变量扩展不同)可能包括嵌套的变量扩展。 所以下面这样是允许的:
'variables' : [ 'foo': '<!(echo Build Date <!(date))', ],
扩展为:
'variables' : [ 'foo': 'Build Date 02:10:38 PM Fri Jul 24, 2009 -0700 PDT', ],
示例
{
'sources': [
'!(echo filename with space.cc)',
],
'libraries': [
'!@(pkg-config --libs-only-l apr-1)',
],
}
可以扩展为:
{
'sources': [
'filename with space.cc', # 非 @,扩展成一个单一的字符串
],
'libraries': [ # @ 被使用了,所以每个lib都有一个单独的列表项
'-lapr-1',
'-lpthread',
],
}
8. 条件语句
条件使用与变量扩展相同的一组变量。与可变扩展一样,有两个阶段的条件扩展:
- “Early” 或 “pre” 条件,在
coditions节点中引入 - “Late”、“post” 或 “target” 条件,在
target_conditions节点中引入
每种类型的语法都是相同的,只是用于识别它们的键名和评估时间不同。 早期和晚期描述了评估的两个阶段的不同之处。
9. 变量扩展的时机及条件
9.1 早期和晚期
GYP执行变量扩展和条件评估的两个阶段:
- “Early” 和 “pre”阶段在
conditions部分和变量扩展有中的>符号 - “Late”、“post” 和 “target”阶段在
target_conditions部分,>变量扩展,和!命令扩展
示例
对于以下输入:
{
'target_defaults': {
'target_conditions': [
['_type=="shared_library"', {'cflags': ['-fPIC']}],
],
},
'targets': [
{
'target_name': 'sharing_is_caring',
'type': 'shared_library',
},
{
'target_name': 'static_in_the_attic',
'type': 'static_library',
},
]
}
条件只需要在目标环境中进行评估; 在目标上下文之外是无意义的,因为没有定义_type变量。target_conditions允许将评估推迟到targets部分合并到target_defaults的副本之后。 由此产生的目标,在“late”阶段处理之后:
{
'targets': [
{
'target_name': 'sharing_is_caring',
'type': 'shared_library',
'cflags': ['-fPIC'],
},
{
'target_name': 'static_in_the_attic',
'type': 'static_library',
},
]
}
9.2 扩展和评估同时
在扩展和评估阶段,扩展和评估都是同时进行的。 在字典中处理变量扩展和条件评估的过程是:
- 加载自动变量(带有下划线的变量)。
- 如果一个
variables部分存在,递归到它的字典中。这允许conditions在variables字典中出现。 - 从
variables部分加载变量用户定义的变量。 - 对于字典中的每个字符串值,执行变量扩展,如果在“late”阶段操作,则《命令扩展。
- 重新加载自动变量和变量用户定义的变量,因为变量扩展步骤可能会导致自动变量的更改。
- 如果
conditions或target_conditions部分(取决于阶段)存在,则递归到其字典中。这是在变量扩展之后完成的,以便条件可以利用扩展的自动变量。 - 评估条件。
- 重新加载自动变量和变量用户定义的变量,因为条件评估步骤可能导致自动变量的变化。
- 递归到尚未处理的子字典或列表中。
10. Dependencies和依赖
在GYP中,“依赖”是依赖于其他目标("dependencies")的目标。依赖者声明他们依赖目标字典中的特殊部分。
10.1 依赖设置
将设置传递给其依赖是有用的。例如,目标可能要求其所有依赖项都将某些目录添加到其包含路径中、与特定库链接、或者定义某些预处理器宏。GYP允许用“依赖设置”部分正常处理这些情况。 这些部分有三种类型:
direct_dependent_settings,它将设置通告给目标的直接依赖者。all_dependent_settings,它将设置通告给所有目标的依赖者,包括直接的和间接的。link_settings,其中包含当目标的目标文件用作链接器输入时应该应用的设置。
在某些情况下,目标需要将其依赖设置传递给自己的依赖。当目标自己的公共头文件包含依赖关系提供的头文件时,可能会发生这种情况。export_dependent_settings允许目标声明direct_dependent_settings应该被传递给它自己的依赖的依赖。
在大多数情况下,会使用direct_dependent_settings。多数情况下,all_dependent_settings实际上是正确的;如果使用它,最好声明export_dependent_settings。大多数libraries和library_dirs部分应放在link_settings节点内
示例
对于:
{
'targets': [
{
'target_name': 'cruncher',
'type': 'static_library',
'sources': ['cruncher.cc'],
'direct_dependent_settings': {
'include_dirs': ['.'], # dependents need to find cruncher.h.
},
'link_settings': {
'libraries': ['-lm'], # cruncher.cc does math.
},
},
{
'target_name': 'cruncher_test',
'type': 'executable',
'dependencies': ['cruncher'],
'sources': ['cruncher_test.cc'],
},
],
}
在依赖设置处理之后,cruncher_test的字典将是:
{
'target_name': 'cruncher_test',
'type': 'executable',
'dependencies': ['cruncher'], # implies linking against cruncher
'sources': ['cruncher_test.cc'],
'include_dirs': ['.']
'libraries': ['-lm'],
},
如果cruncher定义为shared_library而不是static_library,cruncher_test目标中将不包含-lm,是相反,cruncher本身会链接到-lm。
11. 链接依赖
依赖关系的确切含义随着关系两端的targets的类型(types)而变化。在GYP中,以下两点可以决定有依赖关系目标如何相互关联:
- 依赖目标是否需要与依赖关系链接。
- 依赖目标是否需要在依赖关系之前建立。如果前一种情况属实,这种情况也必须是真实的。
其中,第1项分析由于静态库和共享库的差异而变得复杂。
- 静态库只是用作链接器(
ld或link.exe)的输入对象文件(.o或.obj)的集合。静态库不链接到其他库,它们被收集在一起,并最终链接共享库或可执行文件时使用。 - 共享库是链接器输出,必须经过符号解析。它们必须链接到其他库(静态或共享),以便于符号解析。它们可以在随后的链接步骤中用作库。
- 可执行文件也是链接器输出,也可以进行符号解析。像共享库一样,它们必须链接到静态库和共享库以便于符号解析。在随后的链接步骤中,它们不能作为链接器输入重用。
GYP执行称为“静态库依赖性调整”的操作,其中它使每个链接器输出目标(共享库和可执行文件)直接或间接地链接到它所依赖的静态库。由于可链接的目标链接在这些静态库上,它们也是静态库的直接依赖关系。
作为此过程的一部分,GYP还能够删除两个静态库目标之间的直接依赖关系,因为依赖的静态库实际上并不需要链接到依赖关系静态库。这种移除有利于在一些构建系统下快速构建,因为他们现在可以自由地并行构建两个目标。在某些情况下,删除此依赖关系是不正确的,例如,依赖目标包含生成从属目标所需的头文件的rules或actions时,在这种情况下,依赖目标必须将自身声明为hard_dependency。此设置指示GYP不会删除其生成的输出中的两个静态库目标之间的依赖关系链接。
12 加载文件以解决依赖性
当GYP运行时,会加载dependencies部分中解决依赖关系所需的所有.gyp文件。这些文件不会合并到引用它们的文件中,但可能包含合并到相关目标字典中的特殊部分。
13. 构建配置
TODO
14. 列表过滤器
GYP允许列表项通过“exclusions”(排除)和“patterns”(模式匹配)两种方式进行过滤。任何包含字典中字符串值的列表都可能应用此过滤。由排除或模式修改的清单被称为“基本列表”,与在其上运行的“排除列表”和“模式列表”形成鲜明对照。
- 对于基本列表,由键名称
key和key!提供排除。 - 对于基本清列表,由键名称
key和key/提供了基于模式匹配的正则表达式过滤
key!和key/可能会同时存在。排除列表将首先被处理,然后是key/模式列表。
15. 排除列表(!)
排除列表提供了根据精确匹配从相关列表中删除项目的方法。在排除列表中找到的任何项目都将从相应的基本列表中删除。
示例
根据OS变量的设置从sources中排除文件:
{
'sources:' [
'mac_util.mm',
'win_util.cc',
],
'conditions': [
['OS=="mac"', {'sources!': ['win_util.cc']}],
['OS=="win"', {'sources!': ['mac_util.cc']}],
],
}
16. 模式列表(/)
模式列表与排除列表类似,但功能更强大。模式列表中的每个项目本身都是一个双元素列表:第一项是一个字符串,可以是'include'或'exclude',指定要进行的操作;第二项是一个指定正则表达式的字符串。匹配正则表达式模式的基本列表中的任何项目将根据指定的操作被包含或排除。
模式列表中的项目按顺序进行处理,后面包含的排除项目不会从列表中删除(除非再次将其排除)。
模式列表在排除列表之后进行处理,因此模式列表可能会重新包括之前由排除列表排除的项目。
在排除列表和模式列表中的所有项目都被评估之前,实际上没有从基本列表中删除。这使得每项即使在被排除和随后被包括之后也能保持彼此相对的正确位置。
示例
{
'sources': [
'io_posix.cc',
'io_win.cc',
'launcher_mac.cc',
'main.cc',
'platform_util_linux.cc',
'platform_util_mac.mm',
],
'sources/': [
['exclude', '_win\\.cc$'],
],
'conditions': [
['OS!="linux"', {'sources/': [['exclude', '_linux\\.cc$']]}],
['OS!="mac"', {'sources/': [['exclude', '_mac\\.cc|mm?$']]}],
['OS=="win"', {'sources/': [
['include', '_win\\.cc$'],
['exclude', '_posix\\.cc$'],
]}],
],
}
在应用模式列表之后,根据OS的设置source后将具有以下值:
- 当
OS是linux:['io_posix.cc', 'main.cc', 'platform_util_linux.cc'] - 当
OS是mac:['io_posix.cc', 'launcher_mac.cc', 'main.cc', 'platform_util_mac.mm'] - 当
OS是win:['io_win.cc', 'main.cc', 'platform_util_win.cc']
17. 查找排除项
在某些情况下,GYP生成器需要访问被排除列表或模式列表排除的项目。当GYP在处理这些列表类型排除项目时,会将结果放在一个_excluded列表中。在上面的示例中,当OS是 code>mac时,sources_excluded将被设置为['io_win.cc','platform_util_linux.cc']。
18. 处理排序
GYP使用定义和可预测的顺序执行加载文件和生成输出之间执行的各个步骤:
- 加载文件
- 加载
.gyp文件。合并任何命令行包括到每个.gyp文件的根词典。当找到inludes时,也加载它们,并将它们合并到inludes部分被发现的范围中。 - 执行“early”或“pre”变量扩展和条件评估。
- 将每个字典中的
targets合并到.gyp文件的根target_defaults字典中。 - 扫描每个目标的依赖关系,并对尚未加载的新引用的
.gyp文件重复上述步骤。
- 加载
- 扫描每个目标的通配符依赖关系,扩展通配符。
- 过程相关的设置。这些部分按顺序处理:
- all_dependent_settings
- direct_dependent_settings
- link_dependent_settings
- 执行静态库依赖性调整。
- 对目标字典进行“late”,“post”或“target”变量扩展和条件评估。
- 根据需要将目标设置合并到配置中。
- 处理排除和模式列表。
19. 设置键
设置可能位于任何位置
conditions
conditions节点引入仅基于条件表达式的评估而被合并到封闭范围中的子字典。conditions列表中的每个conditions本身是至少两个项目的列表:
- 包含条件表达式本身的字符串。条件表达式可以采取以下形式:
- 对于字符串值,
var=="value"和var!="value"用于检测相等和不相等。如:当OS被设置为"linux"时,'OS=="linux"'为true - 对于整型值,
var==value、var!=value、var>value、var>=value、var<value、var<=value用于检测相等和不相等 - 条件表达式引用任何未定义的变量都是错误的
- 对于字符串值,
- 包含要合并到封闭范围中的子字典(如果条件表达式值为
true)的字典。
示例
{
'sources': [
'common.cc',
],
'conditions': [
['OS=="mac"', {'sources': ['mac_util.mm']}],
['OS=="win"', {'sources': ['win_main.cc']}, {'sources': ['posix_main.cc']}],
['OS=="mac"', {'sources': ['mac_impl.mm']},
'OS=="win"', {'sources': ['win_impl.cc']},
{'sources': ['default_impl.cc']}
],
],
}
对于以上输入,sources会根据OS变量采用不同的值:
- 如果
OS是"mac",sources会是['common.cc', 'mac_util.mm', 'posix_main.cc', 'mac_impl.mm'] - 如果
OS是"win",sources会是['common.cc', 'win_main.cc', 'win_impl.cc'] - 如果
OS是其它值,如"linux",sources会是['common.cc', 'posix_main.cc', 'default_impl.cc']
