Distributing My Python Scripts as Jar Files with Jython

Distributing my Python scripts as JAR files with Jython?

The best current techniques for distributing your Python files in a jar are detailed in this article on Jython's wiki: http://wiki.python.org/jython/JythonFaq/DistributingJythonScripts

For your case, I think you would want to take the jython.jar file that you get when you install Jython and zip the Jython Lib directory into it, then zip your .py files in, and then add a __run__.py file with your startup logic (this file is treated specially by Jython and will be the file executed when you call the jar with "java -jar").

This process is definitely more complicated then in ought to be, and so we (the Jython developers) need to come up with a nice tool that will automate these tasks, but for now these are the best methods. Below I'm copying the recipe at the bottom of the above article (modified slightly to fit your problem description) to give you a sense of the solution.

Create the basic jar:

$ cd $JYTHON_HOME
$ cp jython.jar jythonlib.jar
$ zip -r jythonlib.jar Lib

Add other modules to the 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

Add the __run__.py module:

# 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

On MS Windows, that last line, setting the CLASSPATH environment variable, would look something like this:

set CLASSPATH=C:\path\to\my\app\myapp.jar;%CLASSPATH%

Or, again on MS Windows, use the Control Panel and the System properties to set the CLASSPATH environment variable.

Run the application:

$ java -jar myapp.jar mymainscript.py arg1 arg2

Or, if you have added your start-up script to the jar, use one of the following:

$ 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

The double -jar is kind of annoying, so if you want to avoid that and get the more pleasing:

$ java -jar myapp.jar arg1

You'll have to do a bit more work until we get something like this into a future Jython [Update: JarRunner is part of Jython 2.5.1]. Here is some Java code that looks for the __run__.py automatically, and runs it. Note that this is my first try at this class. Let me know if it needs improvement!

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);
}
}

I put this code into the org.python.util package, since that's where it would go if we decide to include it in a future Jython. To compile it, you'll need to put jython.jar (or your myapp.jar) into the classpath like:

$ javac -classpath myapp.jar org/python/util/JarRunner.java

Then you'll need to add JarRunner.class to your jar (the class file will need to be in org/python/util/JarRunner.class) calling jar on the "org" directory will get the whole path into your jar.

$ jar uf org

Add this to a file that you will use to update the manifest, a good name is manifest.txt:

Main-Class: org.python.util.JarRunner

Then update the jar's manifest:

$ jar ufm myapp.jar manifest.txt

Now you should be able to run your app like this:

$ java -jar myapp.jar

Packaging a Jython program in an executable jar

I realized what the problem was and I wanted to document it in case anyone else has the same problems.

I was using the jython.jar file that came in the standard installation of Jython, and NOT the standalone jython.jar (the instructions at Using the Jar Method mentions this, but the instructions at Building Jars do not). I am still unsure why copying the Lib/ folder of the standard installation into the jython.jar that came with that installation didn't work on my system. However, once I used the standalone jar, things started to work more smoothly.

Additionally, I was able to get my library to work with the packaged file by doing three things in addition to the steps I laid out in my question:

  1. Exploding the standalone jython.jar and copying the folder with all of my library files into Lib, then create a new jar. This seemed to be the easiest way to include my library and allows me to package everything into a single jar.

  2. I discovered after reading Frank Wierzbicki's answer in Why does Jython refuse to find my Java package? that because I am now using the standalone jar, I could no longer use imports of the style from java.awt import *, instead I needed to fully specify each thing I was importing, for example from java.awt.Font import PLAIN, BOLD, ITALIC. So I went through the library's imports and fixed the few that were of the wrong style.

  3. Now that I am adding my Library directly to the Jar's Lib folder, instead of writing Class-Path: ./myLibrary.jar in othermanifest.mf, I put Main-Class: org.python.util.JarRunner as per Frank Wierzbicki's answer in the post I mentioned in my question: Distributing my Python scripts as JAR files with Jython?

This allowed me to create a double-clickable executable jar containing my library and jython file that I wanted to run.

Bundling python files inside jar for access via jython

You can simply put test.py at the root of the jar, with the other scripts organized as they would be on the fs in normal python.
Then you can do something like that:

public static void main(String[] args) {
org.python.util.PythonInterpreter python = new org.python.util.PythonInterpreter();
python.exec("import test");
python.exec("test.callsomthing()");
}

the import in test.py should work as normal, including importing other modules from test.py.

How to distribute a java package including jython code in one jar?

I finally found a solution for my problem. Maybe this is also of interest for someone else.
Just as a reminder, this is meant for a mixed java/jython package with java on top that will then be delivered to a customer, NOT for a self-contained application.

1) All jython sources are put in a separate folder on the top level within the jar file using the tool "jar" (or any other zip tool); I used the folder "Lib".

2) Access to the jython code from java is done using an object factory class modeled as a singleton (similar to the one described here); access to java from jython works straightforward with just the full package name.

3) In the constructor of the object factory I use

String jarPath = myObjectFactory.class.getProtectionDomain().getCodeSource().getLocation()
.getPath();

to determine the location of the jar file from within the code.

4) I add the "Lib" folder within the jar file to the jython module lookup path using

PySystemState newState = new PySystemState();
newState.path.insert(0,Py.newString(jarPath + java.io.File.separator + "Lib")); Py.setSystemState(newState);

As long as the jython.jar file is included in the classpath, this will work.

Packaging and deploying a Jython program from Eclipse

I've made some headway on getting this all working so I thought I would put some notes here in case they help anyone else out. I'd still like to hear from others on their experiences trying to put together something like this.

It turns out that Eclipse as of 3.5 has a project export option for Java -> Runnable JAR File. If you use this option, you can point to a Java main class in the export wizard. You also have the option to have it repackage all the JARs that you are dependent on in your new JAR file. Make sure to check the box to save the export as an ANT build so that you can repeat the process quickly. NOTE that the first time you do this through the interface, it may fail, but it will still have created a JAR file.

Now here's where it gets strange. To track all the dependencies, I am still using a mostly incomplete Maven build in my project. I create the Maven .POM file. And I told Maven what my external JAR dependency was. I then told Maven to do a dependency update for me. It pulled everything into my Maven repository as expected.

Now when I do my ANT build, it appears that it is getting its list of JARs to include in the final build from Maven. I'm not really sure if it is supposed to work that way. I'm also not 100% sure that it is working. I guess I'll find out when I have to add another external JAR to the project.

Anyways, if you follow this question you'll see that you can take the latest builds of Jython and pull the org.python.util.JarRunner.java file out and use it in your own project. This is you Java.main class that you will need to point your ANT build at. From there, convert your main Python/Jython script to be the run script that was talked about in that question.

Next, build up another copy of the Jython JAR file in your Jython directory. This one should have the /Lib directory pulled into the JAR. Save that off and then point your Eclipse IDE Jave Build option for your PyDev project at that JAR as an external dependency. Your JarRunner will now work and execute the run.py file under Jython.

If all that works, you should then be able to rerun the ANT exported build from earlier and you will end up with a single JAR file that you can execute on the command line as:

java -jar {yourjar} args

And distribute to your customers without any additional dependencies.

If that all seems a little bit convoluted, it is. If anyone has a better way of setting this all up using Eclipse, please let me know.

How can I add jars dynamically to jython, inside script?

Just add your jar to sys.path, like this:

~ $ jython
Jython 2.5.0+ (trunk:6691, Aug 17 2009, 17:09:38)
[Java HotSpot(TM) Client VM (Apple Computer, Inc.)] on java1.6.0-dp
Type "help", "copyright", "credits" or "license" for more information.
>>> from org.thobe.somepackage import SomeClass # not possible to import yet
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named thobe
>>> import sys
>>> sys.path.append("/var/javalib/some-thobe-package.jar") # add the jar to your path
>>> from org.thobe.somepackage import SomeClass # it's now possible to import the package
>>> some_object = SomeClass() # You can now use your java class

It couldn't get more simple than that :)

In your case you probably want to use the path of your package to find the jar:

# yourpackage/__init__.py

import sys, os
if 'java' in sys.platform.lower():
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"your-lib.jar"))
from jython_implementation import library
else:
from cpython_implementation import library

Hope that helps!

Distributing my JavaGameEngine (uses Jython)

You need to include the relevant jars containing jython in your packaging of the game (for example - RPM, tar.gz, msi - depends on the OS you're using).

Then, you should have some script running your game (using java command line) and include the jython jars at the classpath.

Searching for files in a jar file (Including Jython class files in a jar file to be found)

The Jython Class files are in the jar file, and the search path for Jython doesn't care if they are in jar file or not. All we need is to locate where the Jython classes are, and let Jython know about it.

From the hint of this post(How to get the path of a running JAR file?), one can find the path where the jar file is located. The class files can be found in the location + "py" directory.

For development purposes, the Jython source code in the source directory should also be specified ("src/py").

Sample Image

String runningDir = Simulate.class.getProtectionDomain().getCodeSource().getLocation().getPath();
String jarPointer = "py";
String joinedPath = new File(runningDir, jarPointer).toString();

String pythonSrcPath = "/Users/smcho/code/PycharmProjects/aggregator/src";
JythonObjectFactory.setupPath(new String[]{joinedPath, "src/py"});

After this modification, the Jython could find the classes correctly.

This is the setupPath method for setting up Jython search path.

public static void setupPath(String[] paths)
{
PythonInterpreter interpreter = new PythonInterpreter();

interpreter.exec("import sys;");
for (int i = 0; i < paths.length; i++) {
interpreter.exec(String.format("sys.path.append(\"%s\")", paths[i]));
}
}


Related Topics



Leave a reply



Submit