当前位置: 首页 > 科技观察

【GN+Ninja学习0x03】GN语法与操作学习

时间:2023-03-13 08:20:10 科技观察

了解更多开源请访问:开源基础软件社区https://ost.51cto.comOpenHarmony使用gn+ninja维护构建开源项目。之前没接触过gn+ninja,是时候系统学习一下了。边学习边记录学习过程,希望对同样需要学习gn+ninja的朋友有所帮助。在这篇文章中,我们来学习一下GN的语法和操作行为。推荐阅读原文档GNLanguageandOperation。GN提供了一个扩展的内置帮助文档系统,为每个功能函数和内置变量提供了详细的参考。可以使用gnhelp查看帮助,还可以使用gnhelp、gnhelp、gnhelp查看具体命令、函数、变量的帮助信息。1.设计理念设计理念编写构建文件不应该是一种创造性的努力。理想情况下,两者都应生成具有相同要求的相同构建文件。除非绝对需要,否则不应有任何灵活性。尽可能多的事情应该是致命的错误。构建定义应该读起来更像代码而不是规则。我不想编写或调试Prolog。但是我们团队中的每个人都可以编写和调试C++和Python。构建语言应该对构建应该如何工作有自己的看法。表达任意的东西不一定容易,甚至不可能。与其让一切都变得更复杂以符合外部要求(在合理范围内),我们应该更改源代码和工具以使构建更简单。在有意义的时候需要像Blaze。2.语言GN使用机器简单、动态类型的语言。支持的类型有:布尔值(真、假)。布尔值。64位有符号整数。64位有符号整数。字符串。字符串。列表(任何其他类型)。范围(有点像字典,仅用于内置内容)。范围(有点像字典)。(1)字符串字符串用双引号括起来,使用反斜杠作为转义符。唯一支持的转义序列是:-\"(双引号)-\$(美元符号$)-\\(反斜杠)反斜杠的任何其??他使用都被视为反斜杠。因此,例如,\b不需要进行转义,而大多数Windows路径(如“C:\foo\bar.h”)不需要转义。通过符号$支持简单变量替换,其中美元符号$之后的单词替换为变量的值。如果没有非变量名字符来终止变量名,可以选择使用${}将名称括起来。不支持更复杂的表达式,仅支持变量名替换。a="mypath"b="$a/foo.cc"#b->"mypath/foo.cc"c="foo${a}bar.cc"#c->"foomypathbar.cc"(2)赋值非空除外列表到空列表(a==[]),没有办法获得列表的长度。如果你发现自己想要做这种事情,那意味着你在构建中做了太多的工作。-注意:那个说,列表不提供获取长度的方法,n或者应该得到长度。ListAppend列表支持追加,如下所示。将一个列表附加到另一个列表会将每个列表项附加为第二个列表中的一项,而不是将列表附加为嵌套成员。a=[“第一”]a+=[“第二”]#[“第一”,“第二”]a+=[“第三”,“第四”]#[“第一”,“第二”,“第三”,"fourth"]b=a+["fifth"]#["first","second","third","fourth","fifth"]列表删除也可以从列表中删除项目,如下。在列表中减号运算符“-”搜索匹配项并删除所有匹配项。从另一个列表中减去一个列表会删除第二个列表中的每个项目。如果没有找到匹配项,则会报错,所以在删除列表项之前需要提前知道列表项是否存在。a=[“第一”、“第二”、“第三”、“第一”]b=a-[“第一”]#[“第二”、“第三”]a-=[“第二”]#[“第一”","third","first"]由于无法测试添加列表项,因此可以这样使用:设置一个主要的文件或标志列表,然后删除不适用的文件或标志基于各种条件的当前版本。—注意:这被认为是一种推荐的做法,维护一个主列表,然后只做减法,排除不合适的列表项。这与下面GYP的建议相同。读到这里有点奇怪。在风格上,更喜欢只添加到列表中,让每个源文件或依赖项出现一次。这与Chrome团队过去对GYP的建议相反(GYP更愿意列出所有文件,然后有条件地删除不需要的文件)。ListItemGetlist支持从零开始的下标取值:a=["first","second","third"]b=a[1]#->"second"[]operatorisread-only,cannotbe用于更改列表。它的主要使用场景是当外部脚本返回多个已知值而你想提取它们时。在某些情况下,覆盖列表比附加到列表更容易。为了帮助解决这种情况,将非空列表分配给值为非空列表的变量会产生错误。如果您想解决此限制,请先将目标变量分配给一个空列表。如下:a=["one"]a=["two"]#Error:overwritingnonemptylistwithanonemptylist.a=[]#OKa=["two"]#OK(3)Conditionals条件语句是类似于C语言,如下。在大多数情况下,您可以使用条件语句。如果只需要在特定条件下声明这些目标,甚至可以将整个目标目标置于条件中。if(is_linux||(is_win&&target_cpu=="x86")){sources-=["something.cc"]}elseif(...){...}else{...}(4)循环循环您可以使用foreach循环遍历列表。这是气馁。大多数构建应该做的事情通常可以在没有的情况下完成,如果您觉得有必要,这可能表明您在元构建中做了太多工作。foreach(i,mylist){print(i)#注意:i是每个元素的副本,而不是对它的引用。}(5)函数调用简单的函数调用看起来像大多数其他语言:print("hello,world")assert(is_win,"ThisshouldonlybeexecutedonWindows")这些函数是内置的,用户不能定义新的函数。一些函数包含在以下代码块中:{}。static_library("mylibrary"){sources=["a.cc"]}大多数函数定义目标。用户可以使用下面讨论的模板机制来定义这样的新功能。准确的说,上面提到的代码块{}是作为函数参数来执行函数的。大多数块式函数执行一段代码,并将结果范围用作读取的变量字典。(6)范围和执行范围以及执行文件和函数调用之后的{}块引入了新的范围。范围是嵌套的。读取变量时,将以相反的顺序搜索包含范围,直到找到匹配的名称。变量写入总是进入最内层的范围。除了最内层的作用域外,任何封闭的作用域都不能被修改。这意味着,例如,当您定义一个目标时,您在块内所做的任何事情都不会“泄漏”到文件的其余部分。if/else/foreach语句,即使它们使用{}块,也不会引入新的范围,因此更改将保留在语句之外。3.命名文件和目录名文件名和目录名是字符串,被解释为相对于当前构建文件的目录。有三种可能的形式:相对名称:"foo.cc""src/foo.cc""../src/foo.cc"源树绝对名称:"//net/foo.cc""//base/test/foo.cc"系统绝对名(少见,一般用于include目录):"/usr/local/include/""/C:/ProgramFiles/WindowsKits/Include"4、Buildconfiguration构建配置(一)目标目标是构建图中的一个节点。它通常表示将要生成的某种可执行文件或库文件。目标依赖于其他目标。内置目标类型如下。您可以使用命令gnhelp获取更多帮助。模板可用于创建增强内置目标类型的自定义目标类型。操作:运行脚本以生成文件。action_foreach:为每个源文件运行一次脚本。bundle_data:声明要进入Mac/iOS包的数据。create_bundle:创建Apple/iOS包。executable:生成可执行文件。组:引用一个或多个其他目标的虚拟依赖节点。shared_library:共享库.dll或.so。loadable_module:.dll或.so只能在运行时加载。source_set:一个轻量级虚拟静态库(通常比真正的静态库更可取,因为它构建速度更快)。static_library:.lib或.a文件(通常可以使用source_set)。(2)Configs配置Configs配置是一个命名对象,用于指定标志、包含目录和定义。它们可以应用于目标并推送到相关目标。要定义配置,示例如下:config("myconfig"){includes=["src/include"]defines=["ENABLE_DOOM_MELON"]}要将配置应用到目标,您可以这样做:executable("doom_melon"){configs=[":myconfig"]}构建配置文件通常为target目标指定默认配置列表。可以根据需要向该列表中添加或删除目标。所以在实践中,您通常会使用configs+=":myconfig"附加到默认列表。有关如何声明和应用配置的详细信息,请参见gnhelpconfig。(3)publicconfigspublicconfiguration一个target目标可以将配置项应用到依赖于它的其他target目标。最常见的例子是第三方目标,它需要一些定义或包含头文件的目录才能正确编译。您希望这些配置项既适用于第三方库本身的编译,也适用于使用该库的所有目标。为此,您需要使用要应用的配置项编写配置配置:config("my_external_library_config"){includes="."defines=["DISABLE_JANK"]}此配置将作为“公共”配置添加到目标中。它既适用于目标也适用于直接依赖于它的目标。注意:使用的配置项是public_configs。shared_library("my_external_library"){...#依赖于此的目标应用此配置。public_configs=[":my_external_library_config"]}反过来,可以通过将目标添加为“公共”依赖项来打开依赖目标前进到依赖树的另一个级别。注意:使用的配置项是public_deps。static_library("intermediate_library"){...#依赖于此的目标也从“我的外部库”获取配置。public_deps=[":my_external_library"]}(4)TemplatesTemplates是GN重用代码的主要方式。通常,模板扩展一种或多种其他目标类型。#声明一个将IDL文件编译为源代码的脚本,然后编译那些#源文件。template("idl"){#始终将辅助目标基于target_name,因此它们是唯一的。目标名称#将是调用模板时作为名称传递的字符串。idl_target_name="${target_name}_generate"action_foreach(idl_target_name){...}#你的模板应该总是定义一个名为target_name的目标。#当其他目标依赖于您的模板调用时,这将是该依赖项的#目的地。source_set(target_name){...deps=[":$idl_target_name"]#要求编译源代码。}}通常,模板定义将放在一个.gni文件中,用户将导入使用此文件查看模板定义:import("//tools/idl_compiler.gni")idl("my_interfaces"){sources=["a.idl","b.idl"]}声明模板将在这个Scope的范围内围绕变量创建一个闭包。调用模板时,魔术变量调用器用于读取Scope范围之外的变量。模板通常会将它感兴趣的值复制到它自己的作用域中:template("idl"){source_set(target_name){sources=invoker.sources}}调用的构建文件目录,而不是模板源文件的目录。因此,从模板调用者传入的文件将是正确的(这通常占模板中的大部分文件处理)。但是,如果模板本身有文件(也许它生成了一个运行脚本的操作),则需要使用绝对路径(“//foo/...”)引用这些文件,以说明当前目录在调用过程中是不可预测的。有关详细信息和更完整的示例,请参阅gn帮助模板帮助。5.其他功能(1)导入您可以使用此导入功能将.gni文件导入当前范围。这不是C意义上的包含。导入的文件会独立执行,产生的作用域会被复制到当前文件中(当include指令出现时,C会在当前上下文中执行被包含文件)。这允许缓存导入的结果,并且还可以防止包含的一些更具“创造性”的使用,例如文件被多次包含。通常,.gni文件将定义构建参数和模板。有关详细信息,请参阅gnhelpimport。您的.gni文件可以通过在名称中使用前导下划线(例如_this)来指示导入文件不会使用临时变量。(2)路径处理路径处理通常,您需要创建一个相对于其他目录的文件名或文件名列表。这在运行使用构建输出目录作为当前目录执行的脚本时尤其常见,并且构建文件通常引用与其包含目录相关的文件。您可以使用rebase_path来转换目录。有关更多帮助和示例,请参阅gnhelprebase_path。将相对于当前目录的文件名转换为相对于根构建目录的典型用法是:new_paths=rebase_path("myfile.c",root_build_dir)。(3)Patterns模式用于为自定义目标类型的一组给定输入生成输出文件名,并自动从列表值中删除文件(参见gnhelpfilter_include和gnhelpfilter_exclude)。它们就像简单的正则表达式。有关详细信息,请参阅gnhelplabel_pattern。(4)执行脚本执行脚本有两种方式。GN中的所有外部脚本都是用Python编写的。第一种方法是构建步骤。这样的脚本将接受一些输入并生成一些输出作为构建的一部分。调用脚本的目标使用“action”目标类型声明(参见gnhelpaction)。执行脚本的第二种方法是在构建文件执行期间同步执行它们。在某些情况下,有必要确定要编译的文件集,或者获取构建文件可能依赖的某些系统配置。构建文件可以读取脚本的标准输出并相应地以不同的方式对其进行操作。同步脚本执行由函数exec_script完成(有关详细信息和示例,请参阅gnhelpexec_script)。由于同步执行脚本需要暂停当前构建文件的执行,直到Python进程完成执行,因此外部脚本很慢,应该尽量减少。为防止滥用,可以在顶级.gn文件中将允许调用exec_script的文件列入白名单。有关详细信息,请参阅gnhelpdotfile。您可以同步读取和写入文件,不鼓励这样做,但在同步运行脚本时偶尔需要这样做。一个典型的用例是传递一个文件名列表,其长度超过当前平台的命令行限制。查看gnhelpread_file和gnhelpwrite_file了解如何读写文件。如果可能,应避免使用这些功能。超过命令行长度限制的操作可以使用响应文件绕过此限制,而无需同步写入文件。有关详细信息,请参阅gnhelpresponse_file_contents。6.小结在本文中,我们学习了GN语言语法和脚本操作,支持的变量类型、命名、构建配置等。了解更多开源请访问:开源基础软件社区https://ost.51cto.com。