一、背景
本文整理自Johannes Nicolai在JFrog 2019用户大会上的讲演《DevOps for Non-Hipsters(aka C/C++ programmers)》。
Johannes Nicolai是Github的解决方案架构师,主要负责德语区的用户。他和很多制造业的用户(多数使用C/C++)交流,询问他们在DevOps或持续交付方面的挑战,通常会得到如下的描述:
在嵌入式C/C++领域,花费几十个小时完成一个完整的DevOps流水线并不少见。为某一个提交运行单独的构建和测试几乎是不可能的,通常每次构建都包含了几百个同事所有的提交。而构建时间长的主要原因在于交付包包含了大量的依赖包,而每次构建这些依赖包都需要从头开始重新构建。上述的描述并不限于德语区,Johannes询问了美国制造业的用户,也得到了类似的反馈。
从业界的发展来看,声明式包管理能够很好的解决上述的问题。在交付包中通过声明描述所需的依赖包,在构建时根据声明从包管理系统中获取相应的依赖包,这样能够大大缩短构建时间。Java或JavaScript的开发者很熟悉这样的方式。
对于像Java或JavaScript这样的开发语言,包管理的实现相对简单,包的每一个版本只对应一个二进制文件。而在C/C++中,由于操作系统、架构、编译器等的不同,包的每一个版本会对应多个不同的二进制文件,彼此之间还并不兼容。这也就导致了C/C++的包管理一直是业界公认的难题。
当然,针对C/C++的开发,现在也出现了像Conan这样比较成熟的包管理解决方案。Johannes在本次讲演中首先分析了为什么要在DevOps中引入包管理,然后通过演示介绍了Conan如何通过方便的包管理和开发方式,帮助C/C++程序员实现简洁、高效的DevOps流水线。
二、为什么要在DevOps中引入包管理
现在业界大都在推行敏捷,而在敏捷提出的12条原则中,其中一条就是:通过早期和持续型的高价值工作交付满足“客户”。
通过持续性的交付,首先能够快速发现问题,从而尽早解决问题,而不是每次发布前都要积累大量的问题,从而导致过长的修复时间和交付质量的下降。
其次,用户可能一开始并不是特别清楚自己的需求。通过持续性的交付,用户可以不断的试用来渐进明晰地明确自己的实际需求,从而保证了交付的有效性。
要实现敏捷原则所要求的持续性的交付,我们必须实现持续性、可重复的DevOps流水线。而为了实现这样的DevOps,一个基本原则是要做到“只构建一次二进制文件”。
也就是说,每一个版本的交付包,我们只构建一次。获得其对应的二进制文件后,在DevOps的后续阶段、不同环境中,都应该用且只用这同一个二进制文件。
然而,针对C/C++的应用来说,各种不同的目标环境导致同一个版本的交付包,必然会对应多个互不兼容的二进制文件。在这种情况,要做到仅一次构建,无疑需要借助于良好的包管理解决方案。
通过引入包管理系统,可以为C/C++包的每一个版本预编译好多个与之对应的面向不同目标环境的二进制包,再通过语义化版本及兼容环境的描述,在构建过程中直接获取对应的二进制包,从而能够大大节省构建的时间,保证DevOps流水线的一致性和可重复性。同时,当发现某些问题,如安全漏洞或开源许可证错误时,也可以通过对依赖关系的管理,迅速定位问题的影响范围,提升问题的解决效率。
对于C/C++开发常用的子模块的方式,并不能满足上述DevOps的要求。子模块的方式不能解决构建时间长的问题,不能保证所依赖的库的不可变性,对版本的依赖关系缺乏灵活的定义和管理,对兼容性的分析和处理也缺乏内置的解决方案。
类似的,通过Git LFS来管理C/C++的包也不是一个好的方式。Git LFS缺乏对版本依赖关系的灵活定义和管理,缺乏对兼容性分析和处理的内置解决方案,同样不能解决构建时间长的问题。
因此,要提升C/C++应用的DevOps效率和质量,我们需要引入与Java、JavaScript等类似的包管理解决方案。而目前这一领域发展最快、最受业界关注的就是Conan。
三、Conan——C/C++的包管理方案
Conan具有良好的兼容性,能够与当前C/C++领域应用的各种构建系统和编译器配合使用。
Conan提供了完整的C/C++应用依赖关系管理能力,能够支持语义化版本描述、传递依赖的解析、依赖冲突的分析与解决,以及灵活的范围化版本描述等。
Conan还为C/C++应用的DevOps建设提供了丰富的工具支持:
针对包仓库,提供了原生、开源的Conan Server,同时JFrog的Artifactory、Bintray也提供了功能更为丰富、全面的商业化产品支持;
Conan的客户端,与各种构建系统对接,实现基于Conan的C/C++构建;
Conan提供与Jenkins、Travis CI等工具的对接,实现C/C++应用自动化的、可重复的DevOps流水线。
在Conan的解决方案中,包的每一个版本都根据目标环境的不同,如架构、操作系统、编译器等,预编译好与之对应的二进制包。构建时,Conan客户端只下载与当前目标环境兼容的二进制包,从而在保证一致性的同时,提升了构建的效率。
对于特殊环境,还没有对应的预编译二进制包的情况,Conan通过定义包的Recipe,描述了如何构建该包的二进制包的过程,Conan客户端可以即时构建出一个新的,匹配与当前特殊环境的新二进制包,供应用构建使用。同时,这个新二进制包也可以存回包仓库当中,供后续的构建直接引用。
综上所述,与Java、JavaScript等使用的类似,Conan为C/C++开发者提供了一个成熟的、功能完整、工具完备的包管理解决方案,能够辅助C/C++的开发者创建稳定、高效、一致、可重复的DevOps流水线。
四、如何在C/C++应用中使用Conan
Johannes在演讲中还通过演示,展示了如何基于Conan,实现便捷、高效的C/C++应用的构建。
Johannes所用的例子不是简单的“Hello World”,而是github上一个真实项目:
要使用Conan,我们只需为每一个C/C++应用增加一个conanfile.txt,用以描述其依赖关系:
利用“conan remote add”命令,可以将Conan客户端和Conan的包仓库建立关联,再执行“conan install”,就可以将符合目标环境需求的所有依赖二进制包下载在本地。
在编辑构建参数,如使用CMake构建,就修改CMakeLists.txt,加入conan的配置,就可以集成下载的依赖二进制包,完成C/C++应用的构建。
除了直接引用Conan仓库中已有的包及其二进制文件,利用Conan也可以创建自己开发的Conan包作为库,供其他C/C++应用依赖。Johannes还以github上的另一个项目演示了如何创建自己的Conan包:
要创建Conan的库包,需要为项目增加conanfile.py文件,如上图中的右半部分,改py文件就对应了之前提到的Conan包的Recipe,它除了描述了该包的基本信息之外,还通过函数定义了如何构建该库包得到二进制文件的过程。
通过执行“conan create”命令,我们就可以生成自定义的Conan包作为内部库,再执行“conan upload”将其上传到Conan包仓库,就可以被其他C/C++应用引用、依赖了。
此外,Conan还可以与Jenkins等工具集成,通过自动化、并行的方式,一次性构建出同一版本包,针对不同目标环境的所有二进制文件:
基于Conan的包管理方案,通过与GitHub、Jenkins、Artifactory、Bintray等工具对接,可以实现完整的C/C++应用的DevOps流水线:
通过演示可以看出,在C/C++应用中引入Conan的包管理,方式是直观、简便的,附加的工作负载并不多。而通过与各种工具的集成,可以基于Conan方便地创建C/C++应用的DevOps流水线,满足敏捷的需求。
五、总结
敏捷化是目前业界应用研发的发展方向。通过实施敏捷化,我们可以实现迅速的、持续的产品交付,从而尽早发现问题,尽早解决问题。而且,通过快速、持续的交付,我们也可以获得用户持续的反馈,渐进明细地挖掘和实现用户的真正需求。
对于C/C++应用及开发者来说,基于Conan的包管理方案,以及与DevOps领域工具的集成使用,可以创建便捷、高效、一致性、可重复的DevOps流水线,从而满足敏捷化的需求。
注:本文图片因平台原因不能上传,可关注“JFrog杰蛙DevOps”公众号,每周二在线课堂,干货分享~