说说gyp,再也不用手撸Makefile

Warming up

简要说说 gcc 的过程:

  1. 预处理:主要处理头文件的包含以及宏替换
  2. 编译:将高级语言编译成汇编语言
  3. 汇编:将汇编语言转换成机器指令,此时得到目标文件
  4. 链接:目标文件库文件的链接,最终得到可执行文件

一些有用的选项
: -E 进行预处理,注意,该命令会将结果输出到控制台上,我们要输出到文件里面。
gcc -E ***.c -o xxx.i
: -S 编译, gcc -S xxx.i, 得到 xxx.s 文件
: -c 汇编处理, 生成目标文件 xxx.o 文件
其他选项
: -std 指定采用的语言标准,比如 -std=c++11
: -Wall
: -Werror
: -v 查看版本信息
: -g 生成调试信息,用于 gdb 调试
: -O 选项,用于优化, -O1 -O2 等
: -x 指定源码的编程语言
其他的,找一个 linux 平台 man 手册 man gcc 查看。

gcc 相关的环境变量

  • C_INCLUDE_PATH / CPATH : C 语言头文件查找路径
  • CPLUS_INCLUDE_PATH : C++ 语言头文件查找路径
  • LIBRARY_PATH : 链接阶段,库文件的查找路径
  • LD_LIBRARY_PATH : 运行时查找共享库的路径(这个并不算gcc的路径)

gcc 的编译选项

gcc -I include_path -L lib_path -l lib xxxx.c -o output

  • -I 指定头文件路径
  • -L 指定库文件路径
  • -l 需要链接的库
  • -o 指定输出
  • 注意:这个命令,才是比较关键实用的编译命令
  • 较为完整的: gcc -g -O2 -Wall -std=c++11 -I include_path - L lib_path -l lib src_files -o output
  • 命令的抽象:
  1. cflags: -g -O -Wall -std -fPIC
  2. 编译期间的其他也可以直接放进 cflags: -I -D
  3. ldflags,链接flag: -L … -l … -static
  4. 库文件生成的相关压缩命令,比如 ar, gcc -shared 等等。

库文件

静态库

  • 静态方式链接 gcc -static xxx.c ;
    这种方式,在链接的时候,去找静态库进行链接,这样生成的 elf ,不依赖任何动态库,但是,如果 code
    file 中,存在着某个函数,在动态库中,而并未提供相应的静态库,这种链接方式将直接报错。这样的方式,纯绿色单elf即可直接运行,但是 size 较大。
  • 制作静态库
    先得到目标文件, gcc -c xxx.c 得到 xxx.o
    压缩成静态文件,ar -r libxxx.a xxx.o
    详情: man ar
  • 使用
  1. 直接链接 gcc main.c libxxx.a
  2. 使用编译选项链接 gcc [-static] main.c -I . -L . -l xxx
  3. 采用环境遍历, CPATH, CPLUS_INCLUDE, LIBRARY_PATH
    静态库链接,以静态方式链接,链接完成后,得到的文件,并不依赖原静态库,deploy 时,无需打包发布。

动态库

  • 制作动态库
  1. 编译: gcc -c -fPIC ***.c ; -fPIC 的作用,小模式,生成的目标尽量小。不能忘,注意,很重要,因为现在某些Linux平台,不带该选项,生成动态库时会直接报错。
  2. gcc -shared xxx.o -o libxxx.so
  • 使用
  1. 直接使用,直接报错,动态库无法直接链接
  2. 编译选项链接 gcc main.c -I . -L . -l xxx
  3. 配置环境变量
  • 运行
    直接运行,会报错,因为需要配置 LD_LIBRARY_PATH 环境变量。
  • 动态库,又称共享库,其实可以动态加载的,但是,这不是我们这篇博客需要讨论的。

两种库的对比

静态库:无需跳转,效率较高,并且脱离原静态库文件,发布变得简单,但是目标文件大,修改维护不方便。
动态库:目标文件小,修改维护升级方便,但是,效率略低于静态库,并且,不能脱离动态库,可以采用动态加载的方式,使用灵活。
根据以往做桌面端程序的经验,为了程序启动尽量快,会采用动态延迟加载的策略(不仅仅是库,还有很多的资源文件等)。这个时候,只能使用动态库,而静态库不支持这种方案的。当然,在windows平台上,静态库跟动态库差距就更大了。

gyp

不说 Makefile 了,之所以选择 GYP 就是为了绕过手撸 Makefile的过程。其实无论是 Makefile 到 target 还是从 gyp 到 target,我们关注的都是最终的target。从 source file 到 target 的过程,有决定性影响的是 gcc ,而不是CMake 或者是 gyp,所以,我们需要重点关注gyp 中的 gcc 相关的内容即可。
GYP Document: https://gyp.gsrc.io/

introduction

典型样例

{
    'variables': {
      .
      .
      .
    },
    'includes': [
      '../build/common.gypi',
    ],
    'target_defaults': {
      .
      .
      .
    },
    'targets': [
      {
        'target_name': 'target_1',
          .
          .
          .
      },
      {
        'target_name': 'target_2',
          .
          .
          .
      },
    ],
    'conditions': [
      ['OS=="linux"', {
        'targets': [
          {
            'target_name': 'linux_target_3',
              .
              .
              .
          },
        ],
      }],
      ['OS=="win"', {
        'targets': [
          {
            'target_name': 'windows_target_4',
              .
              .
              .
          },
        ],
      }, { # OS != "win"
        'targets': [
          {
            'target_name': 'non_windows_target_5',
              .
              .
              .
          },
      }],
    ],
  }

以上就是一个 典型的 gyp 文件的结构。
其实是 python 的 dict 数据结构。其实是个 json 结构,但是不大一样的地方是:

  1. 可以采用 # 进行注释
  2. 每个结束元素之后,追加了一个 逗号。

top-level 域的关键字如下

  • ‘variables’: Definitions of variables that can be interpolated and used in various other parts of the file.
    gyp 自定义的一些 变量,用于 gyp 其他地方
  • ‘includes’: A list of of other files that will be included in this file. By convention, included files have the suffix .gypi (gyp include).
    引用其他的 gyp 文件,按照惯例,作为被引用的 gyp文件,一般都以 .gypi 作为扩展名。
  • ‘target_defaults’: Settings that will apply to all of the targets defined in this .gyp file.
    这个字段,有内部的语法规则,说心里话,刚接触这个 gyp,这句话看不懂,没关系,先跳过,往后看就明白了。
  • ‘targets’: The list of targets for which this .gyp file can generate builds. Each target is a dictionary that contains settings describing all the information necessary to build the target.
    这个 targets 字段,就是这个 gyp file 生成构建的目标。每个 target 域,就是一个 python dict。其中包含了所有的需要构建该 target 的所有的必要信息。
  • ‘conditions’: A list of condition specifications that can modify the contents of the items in the global dictionary defined by this .gyp file based on the values of different variablwes. As implied by the above example, the most common use of a conditions section in the top-level dictionary is to add platform-specific targets to the targets list.
    conditions ,首先,是一个 python list。这个是干嘛呢,是根据前面的 variables 域中定义的变量的具体取值,去调整该gyp python dict 中的具体内容。比如以上样例中所说的,如果 windows 平台,那么,我们可以在 variables 里面添加 OS为 win,不过这个OS应该是个内置的变量,并不需要我们去定义。然后构建就按照下面condition 中 OS==win的分支去走了。

典型的可执行程序的样例

{
    'targets': [
      {
        'target_name': 'foo',
        'type': 'executable',
        'msvs_guid': '5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65',
        'dependencies': [
          'xyzzy',
          '../bar/bar.gyp:bar',
        ],
        'defines': [
          'DEFINE_FOO',
          'DEFINE_A_VALUE=value',
        ],
        'include_dirs': [
          '..',
        ],
        'sources': [
          'file1.cc',
          'file2.cc',
        ],
        'conditions': [
          ['OS=="linux"', {
            'defines': [
              'LINUX_DEFINE',
            ],
            'include_dirs': [
              'include/linux',
            ],
          }],
          ['OS=="win"', {
            'defines': [
              'WINDOWS_SPECIFIC_DEFINE',
            ],
          }, { # OS != "win",
            'defines': [
              'NON_WINDOWS_DEFINE',
            ],
          }]
        ],
      },
    ],
  }
  • ‘target_name’:目标名称,在所有.gyp文件中唯一。visual studio 的工程名,或者XCode的工程名。
  • ‘type’:设置为executable,可执行文件,linux 上就是默认生成 a.out 。
  • ‘msvs_guid’:这仅是过渡性的。这是hard code的GUID值,将在生成的Visual Studio解决方案文件中使用。windows 构建工具不熟悉,反正hard code 一个,无所谓了。
  • ‘dependencies’:此列表列出了此目标所依赖的其他目标。gyp生成的文件将确保其他目标在此目标之前构建。dependencies列表中的任何库目标都将与此目标链接。此列表中目标部分中列出的各种设置(defines,include_dirs等)direct_dependent_settings将应用于该目标的构建和链接方式。请参阅下面的的更完整讨论direct_dependent_settings。
    这是一些依赖的处理方式。后面将更加完整的讨论。
  • ‘defines’:gcc -D 后面的宏。个人建议,不要用。试想无论在 IDE 还是 linux shell 中,这种 define 对阅读代码障碍太大。一般的在shell里面, find -name “*.h” |xargs grep --color -n “SOME_MACRO”。或者 .c 或者 .cpp ,结果都找不到这个宏定义,结果在 Makefile里面,崩溃不崩溃?所以这个域,自己敲代码不要随便用,灵活但是易错,并且以产生歧义。所以个人建议,不要用。但是呢,引用了比如其他开源代码,人家要死不死的用了这个,我们也要知道。
  • ‘include_dirs’:包含头文件所在的目录。就是 gcc -I 后面的 include_path 。
  • ‘sources’:此目标的源文件。
  • ‘conditions’:条件构建,比如前面所说的不同平台,或者 debug 跟 release的不同版本,优化等级等等。

库文件的样例

{
    'targets': [
      {
        'target_name': 'foo',
        'type': '<(library)'
        'msvs_guid': '5ECEC9E5-8F23-47B6-93E0-C3B328B3BE65',
        'dependencies': [
          'xyzzy',
          '../bar/bar.gyp:bar',
        ],
        'defines': [
          'DEFINE_FOO',
          'DEFINE_A_VALUE=value',
        ],
        'include_dirs': [
          '..',
        ],
        'direct_dependent_settings': {
          'defines': [
            'DEFINE_FOO',
            'DEFINE_ADDITIONAL',
          ],
          'linkflags': [
          ],
        },
        'export_dependent_settings': [
          '../bar/bar.gyp:bar',
        ],
        'sources': [
          'file1.cc',
          'file2.cc',
        ],
        'conditions': [
          ['OS=="linux"', {
            'defines': [
              'LINUX_DEFINE',
            ],
            'include_dirs': [
              'include/linux',
            ],
          ],
          ['OS=="win"', {
            'defines': [
              'WINDOWS_SPECIFIC_DEFINE',
            ],
          }, { # OS != "win",
            'defines': [
              'NON_WINDOWS_DEFINE',
            ],
          }]
        ],
    ],
  }

注意
1 type字段的取值 <(library),在 Linux 平台上,type可以将显式设置为static_library或shared_library
2 ‘direct_dependent_settings’: This defines the settings that will be applied to other targets that directly depend on this target–that is, that list this target in their ‘dependencies’ setting. This is where you list the defines, include_dirs, cflags and linkflags that other targets that compile or link against this target need to build consistently.
‘direct_dependent_settings’:这个字段定义了一些设置,用于给直接依赖该target的其他target参考使用。也就是说,直接依赖该target的模块,应直接将该list中的内容直接列入到其 dependencies 的setting中。
这个字段要小心,一致性构建,gcc 的编译以及链接选项那么多,大型项目,各种依赖,一定要注意,这类编译期间的选项的设置,宏的配置等等。这个是直接影响到我们的 目标的。我们应该小心设置。
3 ‘export_dependent_settings’: This lists the targets whose direct_dependent_settings should be “passed on” to other targets that use (depend on) this target. TODO: expand on this description.
这个python list 中存放的是一些 targets(目标),这些目标的 direct_dependent_settings 设置,应该被 传送 到 其他的使用(或者说依赖)该 target 的targets(目标)中。(这个文档没有开发完善,这个地方,有个todo,说明,需要进一步阐述这段文字。)
这两个配置很重要,也是需要单独说一下的。
我们也别 target 了,就是模块,我们用ABC去区别一下,省着英语里面的一堆定语从句,这翻译起来,也确实拗口。
我们现在写 gyp 的代码模块是A。2中所提到的,直接依赖该模块A的target模块,我们称之为B。
按照2中的描述,当我们为其他模块,比如B 提供功能库时,需要将本模块的一些编译选项,设置,定义在当前A模块的 direct_dependent_settings中,如果B模块使用了我们的当前的这个A模块,那么,建议B模块将我们在这个字段中定义的这些设置,直接放到B模块其自身的dependency的settings中。
这就是2中描述的内容。
3中的描述,ABC三个模块,延续2中的关系,B依赖A,也就是说 A是B的一个库。3描述的这个字段,里面给的不是设置,而是模块名。B依赖了A,那么A中的这个 direct_dependent_settings,需要在B dependency 中的 setting 中被设置,并且B的export_dependent_settings,要把A模块放进去。那么模块C 在依赖B的时候,第一,需要查看B的 direct_dependent_settings 中的设置,并且,还有查看 B的export_dependent_settings的模块,A,然后再去找A的direct_dependent_settings 中的设置,这两个设置,其实都要在C的dependency的settings中体现出来。
(我是这么理解的,那么,到底是不是这么回事呢?请各位看官,自行阅读英文,自行深入理解。)

Use Cases

跨平台特性

  • 后缀名规则
    当源文件名符合以下格式***_linux.{ext}, *_mac.{ext}, *_posix.{ext} or *_win.{ext}**。
    比如 some_operations_win.c 那么,在非 win 平台的gyp处理时,就会直接被排除掉。gyp 提供了一种机制,我们不需要再conditions里面去添加源代码文件,而是采用这种方式,去实现跨平台的不同文件的自动选择。
    这个特性很好,但是,这个特性,在隐性的定义一些规则。这些规则,在影响我们编码。对于跨平台编码而言,对于coding人员而言,除非真的是平台独有,否则,一般是不需要这种完全独立的平台文件去进行不同的包含的。所以,我们在处理这种情况的时候,酌情采用这种规则。虽然,一个工具在反向规定我们的编码规则,但是,这个规则,一目了然,所以关碍不大。
  • 移除规则:(推荐使用)
    移除规则的特点就是:所有文件放在一个 source list 下,然后,根据平台去剔除。
  {
    'targets': [
      {
        'target_name': 'foo',
        'type': 'executable',
        'sources': [
          'linux_specific.cc',
        ],
        'conditions': [
          ['OS != "linux"', {
            'sources!': [
              # Linux-only; exclude on other platforms.
              'linux_specific.cc',
            ]
          }[,
        ],
      },
    ],
  },

如上样例: OS 不是 linux 的时候,source! 表示需要剔除的文件list,里面给了一个 linux_specific.cc ,就需要被剔除掉。
这个是 gyp 的推荐规则。

  • 特定平台特有包含规则:(不推荐)
    这种规则,就是通用跨平台的放在一起,然后针对不同平台的,独立去放在对应conditions条件下的list中。
    样例:
  {
    'targets': [
      {
        'target_name': 'foo',
        'type': 'executable',
        'sources': [],
        ['OS == "linux"', {
          'sources': [
            # Only add to sources list on Linux.
            'linux_specific.cc',
          ]
        }],
      },
    ],
  },

settings 的设置,即编译选项的设置

  • -D 选项
    -D 选项设置字段
  • -I 选项
    -I 选项设置字段
  • cflags 选项
    cflags 选项设置字段
  • ldflags 选项,链接选项
    ldflags 选项设置字段
    其实,user document 部分,基本就这么多了,也基本都够用了。

Example

TO-DO,等后续吧。。。。
下面我们看一看其官网说明上的 GYP-Language Spec。

GYP Lang-Spec

TO-DO,(Sorry, XX软件down掉了,这个网站访问不了,等后续吧)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值