包管理与路径管理

Sat 22 January 2011
  • 装备 tags:
  • java
  • maven
  • nodejs
  • python
  • tools published: true comments: true

现在几乎每一种语言都有一些依赖管理工具,或者是中央的包仓库。比如这些:

  • Java: maven, ivy, gradle
  • Ruby: gems
  • Python: easy_install, pip
  • Clojure: leinigen
  • Groovy: maven, grape, gradle
  • Hashkell: cabal
  • PHP: pear
  • Nodejs: npm

这些工具在管理包和路径时都会采用各不相同的策略,有的是通过自身实现,有的是借助语言平台本身的特点。

Java

其中最注明的Maven,它的方式是在POM文件中定义你的依赖,Maven会在本地仓库中维护这些依赖。Maven的本地仓库默认是在$HOME/.m2/repository目录下,是用户独立的,当然要下载一个依赖也不需要root权限。而在通过Maven运行Java项目时,Maven插件会自动管理classpath,你并不需要把这些依赖从本地仓库里拷贝出来而单独维护一个所谓lib目录(这样也不好管理)。这是Maven的方式,目前看来,这也是最经典的一种方式。除了maven本身的特性以外,这也和Java的classpath机制有关。

最近势头很猛的Java构建工具gradle,方式也与Maven类似,它会把下载的依赖存放在$HOME/.gradle/cache目录里,并自动管理classpath。

同样是Java,与maven相对应的是ant+ivy。ivy可以为你管理依赖,但是ivy不会帮你管理classpath。ivy的包管理,是以project为scope的,你需要维护一个lib目录来存放这些下载的包,再通过传统的ant的方式去管理classpath,从而使项目可以进行编译和运行。

在Java世界里,还有一个特别的工具叫做grape,它是专门用于groovy的轻量级的依赖解决方案。grape是以脚本为scope,在需要依赖的脚本中通过@Grab声明依赖,grape工具可以从maven仓库中下载依赖到$HOME/.groovy/grapes中,并把相关的依赖加入groovy的classpath。除此以外,grape还有一个命令行工具帮助你手动下载依赖到本地仓库。grape的内部是基于ivy的,不过它的方式比ant要自动化很多。

在Java世界里还有一个特例,是Clojure的依赖管理工具leiningen。leiningen本身也比较简单,它的方式与ivy相同,会解析project.clj文件中定义的依赖关系,并下载到当前的工程目录下的lib中。lein是鼓励通过uberjar的方式把依赖统统打包的,所以它并没有classpath的管理功能。

总体来说,Java世界的工具和Java是相似的,其最大特点就是System independent,安装包不需要root权限,每次的运行都需要管理classpath。作为开发人员,classpath中有哪些可以访问的类库是可以控制的,这也使Java程序的移植性得到良好的控制和管理。

Python

与Java不同,Python通常作为系统分发的一部分,他的包管理和PATH管理要相对混乱一些。通常我们有两种方式来安装一个Python的软件包:
  • sudo apt-get install python-redis
  • sudo easy_install redis
一种是通过系统的包管理工具(如apt-get)从系统的软件仓库里安装,一种是通过python自己的包管理工具(如easy_install / pip)从Python Cheese Shop中下载安装。这两种安装方式有什么不同呢。以Ubuntu为例,通过apt-get安装的python包通常会被放在 /usr/share/pyshared 或 /usr/lib/python2.6/dist-packages 中,相对应的,由easy_install安装的Python包,则存放在 /usr/local/lib/python2.6/dist-packages 中。Python启动后可以通过查看sys.path来了解当前的path情况。

除了安装到系统目录,easy_install可以通过 --user 选项来把软件包安装到用户目录 $HOME/.local/lib/python2.6/site-packages。不过无论是系统级别还是用户级别,python都很难在启动时管理Path,即任何时候python都可以访问安装在系统中的所有软件包。这导致了混乱的情况,导致编写的python软件难以进行依赖管理和移植(即使没有定义在setup.py中,很多依赖还是可以访问的)。

由此virtualenv营运而生,virtualenv帮助你创建一个独立的python运行环境。激活这个小环境之后,easy_install/pip仅仅安装软件到小环境,python仅能访问环境内部的site-packages,这样整个环境中的依赖关系就非常清楚,也保障了程序的移植性。这样,就将原本系统scope的python包管理级别改进为项目级别。我之前写的jip也是将依赖下载或拷贝到virtualenv的小环境中,并且修改jython的启动脚本修改PYTHONPATH的设置,保证Java依赖对Jython的透明可访问。

Nodejs

nodejs是一个新兴的生态系统,一个包管理工具对其也是必不可少。npm是目前整个社区都比较认可的工具。

不过目前npm并不好用。npm默认会把自己安装到 node安装前缀的目录,比如node安装时你选择了默认前缀/usr/local,那么npm会把自己安装到/usr/local/lib/node里。这个目录是系统级别的,所以需要root权限,而npm本身又不鼓励用户用root权限来安装软件包(安全问题)。所以作者说希望用户把/usr/local/lib/node权限授予用户,或者把node安装到用户目录里。这两种方式其实都不太优雅。

Ruby的gems在这方面最符合unix哲学,即用户知道自己在做什么。如果用户以root权限运行gem install,gem会把软件包安装到系统目录中对所有用户可用,而如果以普通用户权限运行,则安装到用户目录 $HOME/.gem 中仅当前用户可见。

nodejs在加载软件包时,会在require.paths中的几个目录里查找,前两个都是用户目录,所以npm也并非一定要把包安装到系统目录里去。虽然现在可以用过修改.npmrc文件在修改npm的默认行为,不过在这个CoC的时代,显然太繁琐了。

Best Pratice

总结一下,包管理和路径管理的最佳实践应该是:语言平台有CoC的路径机制,包管理器有基于环境变量、用户权限的判断执行合适行为。