Posts Tagged ‘ CPAN

包管理器杂谈

前言

今天我想思维跳跃一下,讲讲包管理器,这是一个相当有趣的话题。因为程序员年复一年的写程序,大多是为了娱乐他人,好吧,有的人管那叫创造价值,有的人叫养家糊口。其实没所谓了,本质上来说大部分程序员并不是自己程序的受害者,所以你知道为什么这个世界上有那么多烂程序了吧。让我们回来,聚焦在那些极少的程序员写给自己的程序,除了编译器,版本控制工具以外还有一种非常非常重要,那就是包管理器。

你知道的这个世界上真正了解编译器的人据说坐不满一间教室。我想了解包管理器得人也多不到哪去。首先我们要看看今天要出场讨论的一些候选人。

  • apt 具有超级牛力Debian系猛男
  • yum 为Redhat延寿20年的黄狗
  • homebrew Mac下的青年才俊
  • rubygems Ruby下的魔法大师
  • npm Node.js的超级保镖
  • elpa Emacs的小宝宝

我今天要忽略一些内容,很可能引起某些人的不满。比如像Perl,Python,PHP,Haskell,Lua,R都有一些类似的设施,所以就不一一讨论了。明眼人也许看出来了,六个男嘉宾可以分为两组。一组是系统级别的(system-level),一组是应用级别的(application-level)。维基百科基本上对系统级别的包管理器做了一些介绍,应用级别的仅仅是列举了几个,并没有详细的介绍。但我认为包管理器都是完成类似的工作所以放到一起来讨论。

验证

从单个包的角度来说,一定要确保其来源,否则会轻易受到中间人攻击。所以一般的包管理器都具备简单的验证环节。常见的手法是MD5校验,或者其他Hash校验,homebrew提供的大部分安装包都是用这样的方式校验的。较为严格的应做签名验证,apt和yum都有相应的机制来验证软件包是否来自可信的源。综合来讲Hash的方式加密强度较低,而且严格说来这种校验只应作为下载的完整性检查,并属于授信操作。

依赖性

可以说包管理器解决最为重要的问题就是依赖性关系的问题。我相信在没有yum的RedHat中世纪,大家需要参考n多资料才能正确编译内核。那个时候他们是真心羡慕Debian的。在elpa出现之前,配置一个贴心的emacs环境简直就被视为高手的验证标准。依赖性关系其实并不像看上去的那样容易解决。很多年来make为了解决编译依赖性关系遭受不少的冷嘲热讽。尤其是这种依赖性关系比较松散的时候,看上去就不那么好解决。A依赖于B的3.2.1以上但不超过3.3的版本,这种有时候行有时候不行的条件判断构成了一个非常复杂的逻辑关系。但包管理器必须处理好这个关系,知道哪些依赖条件没有得到满足。依赖性关系的管理是一个生态系统的核心价值。Perl有了CPAN所以带来了一个非常美好的世界,甚至有人说CPAN比Perl的语法更吸引他。依赖性可以让程序员不那么纠结去手工寻找所需要的包,而这个过程可能是相当费时,步骤非常繁琐,而且极容易出错的。对,就好比不使用make工具来编译内核,不是不可能,但阿汤哥会做一些更有利于世界和平的事情。依赖性带来的好处就是开包即用,开袋即食,完全不必担心由于依赖性造成的破损。我猜测由于包管理器提供了一个可信的,可重复的,健壮的依赖性管理,每年程序员节省下来更多时间用于创造新的垃圾代码。

多版本

传统上的包管理器的互斥关系管理有点粗放,apt和yum在处理升级之类的问题时基本就是粗暴地将老的版本替换掉。当然这样设计是比较简单的,可是我们的系统并不美好,经常会有一些遗留软件需要老版本的库。而且从实践的角度来讲这也是一个不太难解决的问题,很多人的机器上都有各种版本的软件,所以后来设计的包管理器就注意了这方面的改进,比如homebrew和rubygems都可以支持同一个软件的多个版本,相互不影响,用户可以随意指定使用软件的版本。多版本是松散型依赖关系必然会出现的特性,也是简单粗暴转向优雅设计的拐点。一个随之而来的问题就是清理不用的版本。这是原来简单粗暴方式比较好解决的一个问题。将原来的是非判断变成了一个寻求最优解的优化问题了。

相对独立的环境

有的时候我们希望严格限制程序的运行环境,但由于包管理器为了便于升级和追新设定了比较宽松的限定条件,使得构建相同的运行环境在相当长的一段时间都是一个非常困难的任务。随着云计算的兴起,基础设施代码化这种思潮逐渐深入实践,诸如bundler这样精巧的工具诞生了。环境配置再也没有手工操作了,而且可以确保所有系统完全一样,简直帅呆了。如果你不觉得帅,那你定没有半夜被狗血电话叫起来去修复线上bug,结果发现这是某个库包与开发者的环境不一样导致的。相对独立的环境在开发者之间也非常重要,任何差异都可能导致惨痛的教训,真金白银的教训啊。相信越来越多的开发者会在云端部署自己的应用,构建相同的环境今后会成为开发者的基本要求的。

发布

有的包管理工具还提供非常便利的构建工具,可以快速构建自己的软件包,并将其发布。开发者可以随时贡献自己的智慧。rubygems就是这样的典型。homebrew可以本地构建一个配方,然后发起pull request,一旦获准别人就能使用了。这个对于未来的开发模式非常重要。Node.js社区的蓬勃发展就是很好的例证。实践证明降低参与者的参与成本特别有利于大家为社区做贡献。

源码还是二进制

我倾向于homebrew的源码编译方式,虽然有点慢,遭到一些诟病,不过我觉得看着代码打补丁,编译是我的个人乐趣。

传输问题

考虑到中国网络的多样性问题,在此我讨论四个方面:网速测试,断点续传,多点传输,下载调度。无疑elpa在这几方面做得奇差无比,简直就是各个方面的反面教材。yum提供插件可以检测最快的镜像源,apt做得也还可以。但都不能同时从多个源下载同一个包,有时候我们还是很需要迅雷或者BT的功能的。断点续传不用多说了,算是一个基本功能。同时开启多少个并发就不能智能点么,非得一个或者n个,就不能自动调节么。

甚至我觉得包管理器应该构建于BT或者电驴之上。

关于源

私自搭建一个源应得到鼓励,应该几乎没有成本地加入到整个全球分布式的系统中,源之间的同步应该也是开箱即用的。这方面做得最好的我认为是全球的公钥分发系统,BT和eDonkey次之。

结语

生态系统无论对一门语言还是对一个系统来说都是非常重要的,人们不会凭着一门语言良好的语法就蜂拥而至的。良好的生态系统为他们提供可靠而丰富的工具链,库包,最佳实践。而位于生态系统核心的正式管理依赖性关系的包管理器。本人杂乱地讲述了对各种包管理器一些思考,希望能“抛砖引砖拍”!