如何用Jython将Python脚本打包成JAR文件?
我已经做了快两年的Python程序员了,平时习惯写一些小脚本来自动化处理办公室里重复的工作。现在,看来我的同事们注意到了这一点,他们也想要这些脚本。
他们有的用Mac,有的用Windows,而我是在Windows上写的。我查过用py2exe或者py2app把我的脚本变成可执行文件,但都没让我满意……
我了解到他们的系统上都有JVM(Java虚拟机),那么我能不能用Jython之类的工具,把我的脚本打包成一个单独的可执行JAR文件给他们呢?
这样做可行吗……我的意思是,我对如何用Jython写脚本一点都不了解,之前写脚本的时候也没考虑过这个……这样会遇到什么问题呢?
4 个回答
'jythonc'命令可以把你的.py源代码编译成JVM字节码,这样就可以在任何安装了Java的地方使用了。这个信息我是在这里看到的:http://hell.org.ua/Docs/oreilly/other2/python/0596001886_pythonian-chp-25-sect-3.html
我遇到了类似的问题,我希望能为我的jython应用创建简单的命令行调用,而不需要用户经历jython的安装过程,并且能够让jython脚本在运行时把库依赖添加到sys.path中,以便包含核心的Java代码。
# append Java library elements to path
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "lib", "poi-3.8-20120326.jar"))
在Unix系统上,当我在命令行上明确运行'jython'启动器时,它实际上是运行一个大的shell脚本,以正确形成一个Java命令行调用。这个jython启动器似乎依赖于访问jython的核心安装,通过某种方式的“魔法”,允许在我的.py脚本中运行时正确处理添加到sys.path中的.jar文件。你可以通过以下方式查看这个调用,并阻止执行:
jython --print run_form.py
java -Xmx512m -Xss1024k -Dfile.encoding=UTF-8 -classpath /Applications/jython2.5.2/jython.jar: -Dpython.home=/Applications/jython2.5.2 -Dpython.executable=/Applications/jython2.5.2/bin/jython org.python.util.jython run_form.py
但这仍然只是启动一个JVM并运行一个类文件。所以我的目标是能够调用一个独立的jython.jar,这个文件在我分发的lib目录中,这样用户就不需要进行任何额外的安装步骤,就能开始使用我的.py脚本工具。
java -Xmx512m -Xss1024k -classpath ../../lib/jython.jar org.python.util.jython run_form.py
问题是,这种行为有些不同,以至于我会得到这样的反馈:
File "run_form.py", line 14, in <module>
import xls_mgr
File "/Users/test/Eclipse/workspace/test_code/py/test/xls_mgr.py", line 17, in <module>
import org.apache.poi.hssf.extractor as xls_extractor
ImportError: No module named apache
你可能会说我应该把jar文件添加到-classpath中,实际上我也试过,但结果还是一样。
把所有的.class文件打包到一个jython.jar中的建议对我来说一点吸引力都没有。这会很麻烦,并且会让Java/Python混合应用过于依赖jython的分发版本。所以这个主意是行不通的。最后,在经过大量搜索后,我在jython.org上找到了bug #1776,这个问题被列为关键问题已经一年半了,但我没有看到jython的最新更新中包含修复。尽管如此,如果你在让jython包含你单独的jar文件时遇到问题,你应该看看这个。
http://bugs.jython.org/issue1776
在里面,你会找到这个问题的临时解决办法。在我的案例中,我把Apache POI的jar文件解压到一个单独的lib目录中,然后修改sys.path的条目,指向这个目录,而不是jar文件:
sys.path.append('/Users/test/Eclipse/workspace/test_code/lib/poi_lib')
现在,当我通过Java运行jython,引用我的本地jython.jar时,这个工具运行得非常顺利。现在我可以创建简单的脚本或批处理文件,为我的.py工具提供无缝的命令行体验,用户可以在没有任何额外安装步骤的情况下运行。
关于如何把你的Python文件打包成jar文件,最好的方法可以参考Jython的维基文章:http://wiki.python.org/jython/JythonFaq/DistributingJythonScripts
对于你的情况,我建议你先找到安装Jython后生成的jython.jar文件,然后把Jython的Lib目录压缩到这个jar里,再把你的.py文件也压缩进去,最后添加一个__run__.py
文件,这个文件里放你的启动逻辑(Jython会特别处理这个文件,当你用“java -jar”命令调用jar时,它会执行这个文件)。
这个过程确实比应该的要复杂,所以我们(Jython的开发者)需要想办法做一个好工具来自动化这些步骤,但目前这些是最好的方法。下面我把上面文章底部的步骤复制过来(稍微修改了一下以适应你的问题描述),让你对解决方案有个大概念。
创建基本的jar文件:
$ cd $JYTHON_HOME
$ cp jython.jar jythonlib.jar
$ zip -r jythonlib.jar Lib
把其他模块添加到jar里:
$ cd $MY_APP_DIRECTORY
$ cp $JYTHON_HOME/jythonlib.jar myapp.jar
$ zip myapp.jar Lib/showobjs.py
# Add path to additional jar file.
$ jar ufm myapp.jar othermanifest.mf
添加__run__.py
模块:
# Copy or rename your start-up script, removing the "__name__ == '__main__'" check.
$ cp mymainscript.py __run__.py
# Add your start-up script (__run__.py) to the jar.
$ zip myapp.jar __run__.py
# Add path to main jar to the CLASSPATH environment variable.
$ export CLASSPATH=/path/to/my/app/myapp.jar:$CLASSPATH
在Windows系统上,最后一行设置CLASSPATH环境变量的命令大概是这样的:
set CLASSPATH=C:\path\to\my\app\myapp.jar;%CLASSPATH%
或者,在Windows上,你也可以通过控制面板和系统属性来设置CLASSPATH环境变量。
运行应用程序:
$ java -jar myapp.jar mymainscript.py arg1 arg2
或者,如果你已经把启动脚本添加到jar里,可以使用以下命令之一:
$ java org.python.util.jython -jar myapp.jar arg1 arg2
$ java -cp myapp.jar org.python.util.jython -jar myapp.jar arg1 arg2
$ java -jar myapp.jar -jar myapp.jar arg1 arg2
双写-jar这个命令有点麻烦,如果你想避免这个,可以用更简单的方式:
$ java -jar myapp.jar arg1
在我们未来的Jython版本中会有类似的工具,直到那时你需要多做一点工作。这里有一些Java代码,它会自动查找__run__.py
并运行它。请注意,这是我第一次尝试这个类。如果有需要改进的地方,请告诉我!
package org.python.util;
import org.python.core.imp;
import org.python.core.PySystemState;
public class JarRunner {
public static void run(String[] args) {
final String runner = "__run__";
String[] argv = new String[args.length + 1];
argv[0] = runner;
System.arraycopy(args, 0, argv, 1, args.length);
PySystemState.initialize(PySystemState.getBaseProperties(), null, argv);
imp.load(runner);
}
public static void main(String[] args) {
run(args);
}
}
我把这段代码放在org.python.util包里,因为如果我们决定在未来的Jython中包含它,它就应该放在这里。要编译它,你需要把jython.jar(或者你的myapp.jar)放到类路径中,像这样:
$ javac -classpath myapp.jar org/python/util/JarRunner.java
然后你需要把JarRunner.class添加到你的jar里(这个类文件需要放在org/python/util/JarRunner.class),在“org”目录上调用jar命令会把整个路径打包进你的jar。
$ jar uf org
把这个内容添加到一个文件中,用来更新清单,文件名可以叫manifest.txt:
Main-Class: org.python.util.JarRunner
然后更新jar的清单:
$ jar ufm myapp.jar manifest.txt
现在你应该可以这样运行你的应用程序:
$ java -jar myapp.jar