包管理与路径管理

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

  • 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的路径机制,包管理器有基于环境变量、用户权限的判断执行合适行为。

Update on exaile-soundmenu-indicator and exaile-doubanfm-plugin

Exaile-soundmenu-indicator

As many users complaint about the “minimise to sound menu” issue, I updated exaile-soundmenu-indicator plugin. Now it is basically compliant with specification of SoundMenu: it will keep playing if you click the close button while the player is playing, and will exit if not playing. However, to be able to complete the functionality, you have to commentify the line 1506 of /usr/lib/exaile/xlgui/main.py, which is “return true” of method “delete_event” (On Ubuntu 10.10, exaile 0.3.2.0-ubuntu3). Otherwise, whenever you close the window it won’t exit.

I know this is a bad idea to require user to modify the source code, but it‘s not possible to override the behavior of a GTK callback, especially when you do not have the handler_id of the callback. If you do not mind the incompliant of closing behavior, you can just keep it as is, and exiting by menu and CRTL+Q.

Grab the snapshot of github repository to get the up-to-date version:
https://github.com/sunng87/Exaile-Soundmenu-Indicator

Exaile-doubanfm-plugin

Exaile豆瓣电台插件更新。豆瓣最近调整了登录的策略:

  • 用户在首次访问豆瓣时被设置cookie bid
  • 用户提交登陆表单时被要求提交cookie bid,否则不予通过。

此外,这次更新开始使用HTTPS提交用户登录信息。

https://github.com/sunng87/exaile-doubanfm-plugin,请下载最新0.0.6c。

HTML5 Form in Opera 11

严格意义上我现在已经不能算是个web guy了,不过托Web开放的特性,咱也能评论一下。最近做个小的界面,考虑到要面向未来,HTML5又提供了丰富的表单增强。这些特性中,Firefox 3.6基本上还没有支持,Chromium 8.0有少部分支持,而真正支持比较完整的是Opera 11.

autofocus
页面载入后自动将焦点转到某个input,就如以前的onload时候xxx.focus()

required
指定某个input为必填,提交时会有validation,在Opera11里还会给你这样的警告并且禁止提交:
Screenshot - 01122011 - 10:53:41 PM

email / url
指定某个input的特殊类型,提交时也会有对应的格式检查:
Screenshot - 01122011 - 10:54:41 PM

这样的提示可能满足不了你的替用户着想的产品经理,但是你清楚他帮你省了多少事情。而且,在DiveInfoHtml5里提到,iphone甚至为这个input做了优化,在软件盘上把空格键变得很小(因为输入邮件地址用不着),并且提供了一个@键;这就是工程师的用户体验啊。

placeholder
在input里显示提示文本:
Screenshot - 01122011 - 10:52:18 PM

注意到后面的红色星号了吗。这本来是我设计的一个workaround,既然Firefox 3都还不支持required validation,那么我们通过CSS来显示一个必填的提示:

input[required="required"]:after {
    content: '(*)';
    color: #F00;
}

结果这个伪类最后只有在Opera里实现了预期的效果,看在Stackoverflow上的讨论才得知input的:after和:before伪类只有Opera支持。

HTML5 Form这部分是一个真正务实的标准,你可以看到几乎每一个特性都是我们日常每时每刻都需要,几乎每个网站的JS都要实现的功能。标准是沟通上下游(浏览器和应用开发人员)的契约,一个真正反应相互需求的标准才是有实际价值的。

soldat & gefr

我的这一套stack正在走向完整。上次贴了一张soldat-http的图,现在基于soldat的wsgi服务器也已经有了一个基本可以运行的实现,名字叫做gefr(我的命名出处参考这里)。现在gefr上已经可以跑基于bottle框架的wsgi程序了,也就是说一些基于python的web应用可能可以通过jython来运行在soldat上。为了搞定jython的环境,这几天我还花了不少时间做了jip帮我从maven仓库里自动下载依赖的jar包。

soldat和gefr的代码都放在我的bitbucket上:

此外,这两个项目也分别发布到了sonatype oss仓库python cheese shop

现在还有几个问题:

  • soldat在读buffer的时候先获得buffer的limit,再去读相应长度的buffer有时会出现BufferUnderflowException。这个可能存在线程安全问题,现在还没发现。
  • gefr启动之后可以通过在jvisualvm里找到这个进程,但是绑定profiler之后很诡异的是gefr就不再处理请求了。
  • 直接用soldat的处理http请求,吞吐量可以上万;但是在上面加上jython的gefr,再加上bottle框架,同样的功能吞吐量就剩下原来的十分之一了。就是因为没法做profile,所以还不知道时间花到哪里去了。

简单地 announce 一下,这样我有更多的动力来继续把这两个小东西做好。

jip 0.1

The original idea is to create a standalone jython environment, I took traditional Java tools, ant and ivy, to resolve Java dependencies. But the XMLs seem to be verbose and not pythonic. So I decide to create something like pip, that is, resolves and installs dependencies in a pythonic way. It is jip.

jip will automatically download jars and its non-optional runtime dependencies from Maven repositories. By default, jip will search your local repository and maven central repository for the requested artifact. Also, you can create a configuration file to overwrite this.

Virtualenv is required by jip. You must run jip within a standalone environment, created by virtualenv:
virtualenv -p /usr/local/bin/jython jython-env
cd jython-env
source bin/activate

Don’t forget to activate it.

Then download jip with pip:
pip install jip

Now you have pip for python and jip for java. To install a Java package, just type:
jip install <groupId>:<artifactId>:<version>

groupId+artifactId+version is known as the coordinate of a maven artifact. For example, you need spring-core in your jython development:
jip install org.springframework:spring-core:3.0.5.RELEASE

The jars will be stored in javalib directory:
ls javalib

commons-logging-1.1.1.jar     spring-core-3.0.5.RELEASE.jar
spring-asm-3.0.5.RELEASE.jar

And When you installed jip, I will provide you a jython-all command to include dependencies by default. So use jython-all instead of jython to run your program and the shell.

For traditional Java user, there is a resolve subcommand to download dependencies defined in a pom file. This is more maintainable, some of you may prefer this way to typing it one by one.
jip resolve pom.xml

To define custom repositories, place a dot file .jip in your home directory:

[jboss]
uri=http://repository.jboss.org/maven2/
type=remote

[local]
uri=/home/sun/.m2/repository/
type=local

[central]
uri=http://repo1.maven.org/maven2/
type=remote

You may have internal Nexus, just append to this file following the pattern.

Finally, a clean subcommand to remove everything you downloaded.

That’s all. You can find the project at:

Your feedback is appreciate. Fire an issue in github if you find any bugs or new ideas about this tool.