分布式软件构建第二部分:构建系统如何工作

注:本文英文原文在google开发者工具组的博客上[需要翻墙],以下是我的翻译,欢迎转载,但请尊重作者版权,注名原文地址。

上篇文章中提到了在Google,所有的产品都是从头开始构建的。这篇文章会更深入的介绍Google的构建系统[即Blaze]是如何工作的,并介绍让软件构建过程更快的方法。在后续的文章里,我们会解释如何利用这种确定的信息来在大规模集群之上进行分布式的软件构建并在开发者之间共享构建结果。

问题:Google是如何描述驱动构建和测试的依赖关系的呢?

在Google我们把代码划分成叫做包[package]的小单元。可以把包理解为一个目录,这个目录里面包含了源文件和一个描述文件,描述文件中指定了如何将源文件转换成构建的输出。这个描述文件叫做 BUILD,一个目录中存在这个BUILD文件,就可以把这个目录当作一个包。

所有的包都在同一个文件树下面,使用从文件树的根到包含BUILD文件的目录的相对路径来做为这个包的全局唯一的标示。这说明包名和目录名之间是一一对应的关系。

在BUILD文件中我们用规则[rules]来描述构建后包的输出。使用包名和规则名称可以唯一的标示这条规则。我们把这两者的结合叫做标签[label],我们使用标签来描述规则之间的依赖关系。

来看一个具体例子:

/search/BUILD:
cc_binary(name = ‘google_search_page’,
deps = [ ‘:search’,
‘:show_results’]) cc_library(name = ‘search’,
srcs = [ ‘search.h’,‘search.cc’],
deps = [‘//index:query’]) /index/BUILD: cc_library(name = ‘query’,
srcs = [ ‘query.h’, ‘query.cc’, ‘query_util.cc’],
deps = [‘:ranking’,
‘:index’]) cc_library(name = ‘ranking’,
srcs = [‘ranking.h’, ‘ranking.cc’],
deps = [‘:index’,
‘//storage/database:query’]) cc_library(name = ‘index’,
srcs = [‘index.h’, ‘index.cc’],
deps = [‘//storage/database:query’])

这个例子展示了两个BUILD文件。第一个BUILD文件描述了//search 这个包,包含一个可执行文件和一个库。第二个BUILD文件描述了包含几个库的//index包。name属性来命名规则,deps属性来描述规则之间的依赖关系。使用冒号来分隔包名和规则名。如果某条规则所依赖的规则在其他目录下,就用"//"开头,如果在同一目录下,可以忽略包名而用冒号开头。这样可以清晰的看到各个规则之间的依赖关系。如果你仔细看了上面的例子,可以看到几个规则都依赖于//storage/database:query 这个规则,说明依赖之间构成了一个有向图。这个有向图必须是无环的,这样我们就可以梳理出构建各个目标的顺序了。

下图是上面描述的依赖关系的一个图形化展示:

可以看到,跟基于make的构建系统不同,我们引用的是抽象的实体而非具体构建的输出结果。实际上,cc_library 这样的规则并不需要产出任何输出,它就是一个简单的从逻辑上组织源文件的方式。为了让我们的构建系统以更快的速度构建,这一点尤其重要。尽管各个cc_library规则之间有依赖关系,但我们可以用任意顺序编译所有的源文件。编译 'query.cc'和'query_util.cc'时,我们只需要依赖头文件'ranking.h'和'index.h'。实际上我们可以同时编译这些源文件,除非我们依赖于其他规则的输出文件。

为了真正执行构建所需的步骤,我们把每个规则分解成一个或多个实际的步骤,称之为"行为"[actions]. 可以把行为理解为一条命令和输入/输出文件。一个行为的输出可以是另外一个行为的输入。这样所有的行为就形成了由行为和文件组成的二分图。为了构建目标所需要做的就是从这个二分图的叶子节点--那些不需要任何行为来创建就已经存在的源文件--遍历到根节点并顺序执行这些行为。这保证在执行一个行为之前,相关的所有输入文件都已经就位了。

一个小规模目标组成的行为二分图的例子如下,行为是绿色,文件是黄色。

同时我们确保每个行为对应的命令行参数中的文件名称都使用的是相对路径--源文件使用从源文件目录层级中根目录开始的相对路径。同时由于我们知道一个行为所有的输入和输出文件,所以可以很容易的远程执行任何的行为:只需要把所有的输入文件拷贝到远程机器,然后在远程机器上执行命令,再把输出文件拷贝回用户机器。后续的文章中我们会介绍一些使远程构建更高效的方法。

所有的这些跟 make 所做的事情没有太大区别。最重要的区别是我们不是以文件为单元来确定依赖关系,这就让构建系统在决定行为的执行顺序时,有更大的自由度。在一次构建过程中并发度越高(即行为二分图宽度越宽),执行时间就越短。在Google,通过简单的增加机器就可以扩展构建系统。如果我们数据中心的机器足够多,那么一次构建的时间就主要由行为二分图的高度来决定。

上面描述的是在Google,一次干净的构建是如何进行的。在实际中大多数开发过程中的构建是增量的。这意味着一个开发人员在两次构建中间只修改了小部分源文件,这种情况下构建整个行为图是很浪费的,所以我们只执行跟上次构建相比,输入文件发生变化的的行为。为了达到这个目标,我们跟踪了每次执行行为时输入文件的内容摘要。上篇博客中提到,我们跟踪源文件的内容摘要并使用这个摘要来跟踪对文件的修改。前文描述的FUSE文件系统把每个文件的摘要当作一个扩展的属性。这种设计下构建系统很容易就能拿到一个文件的摘要并且能够让我们不用执行那些输入文件没有改变的行为。

本系列的第三部分会介绍如何使远程执行真正的高效甚至让它提高构建的速度!

PS:从英文原文中看到的一些回复:

根据BUILD文件产生Makefile是很容易的,而从Makefile推导出BUILD文件是几乎不可能的。因为BUILD文件提供的依赖是更高级别的抽象单元(目标和包),这是很难从基于文件的依赖关系中推倒出来的。

Gilbert说,文中提到的构建系统,会有两个问题:

  1. 把实际不需要的依赖库包含进来,除非用工具来自动化的检查
  2. 在低级别的模块中库的使用会有效的聚合依赖,但是它也会让依 赖图不准确,这就会导致过度构建--例如,你修改了一个库的目标文 件,那么依赖于这个库的所有的可执行文件都需要重新链接,但实际上 这些可执行文件可能并不需要这个目标文件。

nyork的答复是:

1确实是一个问题,在问题2的场景下,确实是一种潜在的过度构建。

构建系统和基础设施是跟语言无关的,这是一个非常重要的一点。构建系统应该能够支持C++,Java, shell脚本等。唯一的要求就是执行所需要的底层工具(编译器,连接器等)必须遵守确定的规则(后续的文章会有更多介绍)。完整的定义依赖关系,构建系统就不需要理解每个语言的语义了,这就意味者我们不需要花费CPU时间(和IO)来在工作站上读取并解析源代码。

后续的文章介绍了我们如何使用BUILD文件提供的信息来让构建过程尤其快速和高效,这也说明了为什么相比而言,过度构建根本就不是问题。

回到本系列目录

Google分布式构建软件之二:构建系统如何工作的更多相关文章

  1. Google分布式构建软件之三:分布式执行构建步骤

    注:本文英文原文在google开发者工具组的博客上[需要FQ],以下是我的翻译,欢迎转载,但请尊重作者版权,注名原文地址. 之前两篇文章分别介绍了Google 分布式软件构建系统Blaze相关的为了提 ...

  2. Google分布式构建软件之四:分发构建结果

    注:本文英文原文在google开发者工具组的博客上[需要FQ],以下是我的翻译,欢迎转载,但请尊重作者版权,注名原文地址. 之前的文章,介绍了Google在分布式构建软件过程中,如何把构建过程分发到许 ...

  3. [原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇)

    原文:[原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) .NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) 前言:上一篇文章讲述了一些实现DAL的理论,本 ...

  4. 用POLARDB构建客到智能餐饮系统实践

    在新零售成为大趋势的今天,餐饮行业也加入到这一浪潮之中.智能餐饮系统将帮助餐饮行业从多个维度提升自己的运营能力和收益,而打造智能餐饮系统SaaS化能力也成为了目前的一个热点.本文中果仁软件联合创始人& ...

  5. 利用ant脚本 自动构建svn增量/全量 系统程序升级包【转】

    引文:我们公司是做自己使用产品,迭代更新周期短,每次都花费较多时间和精力打包做增量更新,发现了一篇文章用于 自动构建svn增量/全量 系统程序升级包,收藏之,希望可以通过学习,更加简化我们的工作. 文 ...

  6. 构建自己的embedded linux系统

    [教程]使用buildroot完全自定义自己的embedded linux系统(nand)http://www.eeboard.com/bbs/thread-38377-1-1.html [教程] [ ...

  7. Python flask构建微信小程序订餐系统

    第1章 <Python Flask构建微信小程序订餐系统>课程简介 本章内容会带领大家通览整体架构,功能模块,及学习建议.让大家在一个清晰的开发思路下,进行后续的学习.同时领着大家登陆ht ...

  8. Python flask构建微信小程序订餐系统☝☝☝

    Python flask构建微信小程序订餐系统☝☝☝ 一.Flask MVC框架结构 1.1实际项目结构 1.2application.py  项目配置文件 Flask之flask-script模块使 ...

  9. Pattern Lab - 构建先进的原子设计系统

    Pattern Lab 是一个工具集,帮助您创建原子设计系统.在它的核心,是一个自定义静态网站生成器,构建了类似原子,分子和界面结合在一起,形成模板和页面.Pattern Lab 可以作为项目的模式库 ...

随机推荐

  1. JS挂马攻防

    JS挂马攻防实录 攻现在最多见的JS挂马方法有两种,一种是直接将JavaScript脚本代码写在网页中,当访问者在浏览网页时,恶意的挂马脚本就会通过用户的浏览器悄悄地打开网马窗口,隐藏地运行(图1), ...

  2. 第五篇:在SOUI中使用XML布局属性指引(pos, offset, pos2type)

    窗口布局的概念 每一个UI都是由大量的界面元素构成的,在Windows编程,这些界面元素的最小单位通常称之为控件. 布局就是这些控件在主界面上的大小及相对位置. 传统的布局一般使用一个4个绝对坐标来定 ...

  3. 【iOS】UITabView/UICollectionView 全选问题

    UITabView/UICollectionView 全选问题 SkySeraph July. 30th 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySera ...

  4. LinkedHashMap源码阅读笔记(基于jdk1.8)

    LinkedHashMap是HashMap的子类,很多地方都是直接引用HashMap中的方法,所以需要注意的地方并不多.关键的点就是几个重写的方法: 1.Entry是继承与Node类,也就是Linke ...

  5. 注解 @RequestParam,@RequestHeader,@CookieValue,Pojo,servlet原生API

    1.@RequestParam 我们的超链接:<a href="springMvc/testRequestParam">testRequestParam</a&g ...

  6. sql语句,怎么取查询结果的位置

    SQL 中的 substring 函数是用来抓出一个栏位资料中的其中一部分.这个函数的名称在不同的资料库中不完全一样: MySQL: SUBSTR( ), SUBSTRING( ) Oracle: S ...

  7. python调用c\c++

    前言 python 这门语言,凭借着其极高的易学易用易读性和丰富的扩展带来的学习友好性和项目友好性,近年来迅速成为了越来越多的人们的首选.然而一旦拿python与传统的编程语言(C/C++)如来比较的 ...

  8. Spark性能优化-coalesce(n)

    有时用Spark 运行Job 的时候,输出可能会出现一些空或者小内容.这时重新将输出的Partition 进行重新调整,可以减少RDD中Patition的数目. 两种方式: 1. coalesce(n ...

  9. pycharm快捷键、配置virtualenv环境,配置django调试,配置远程调试

    pycharm安装和首次使用 http://blog.csdn.net/chenggong2dm/article/details/9365437 快捷键: 找文件.代码.引用相关 1.双击shift ...

  10. id选择器、类选择器、属性选择器

    在网页编辑时,通常要对样式进行各种设置.我们借助CSS样式设计中的选择器,就能很好很方便的对它们进行管理和设置了. 今天,跟大家分享一下几个常用的选择器:id选择器.类选择器.属性选择器. id选择器 ...