Welcome to MSDN Blogs Sign in | Join | Help

Static Compilation of IronPython scripts

The ability to compile IronPython scripts into .NET IL and to save them to disk existed in IronPython 1.0 but has been missing in 2.0 so far. With IronPython 2.0 Beta4 this has been added back.

Why would I compile dynamic language scripts?

There are a lot of reasons to compile scripts into a binary form. Shri talks about some of them here. For folks who don't want to distribute source code in plain text this provides one level of obfuscation. To that end, a function called CompileModules has been added to the clr module to compile scripts into executable IL. The signature of the function is:

CompileModules(str assemblyName, dict kwArgs, Array[str] filenames)

So to compile a file foo.py into foo.dll you would do this:

import clr
clr.CompileModules("foo.dll", "foo.py")

This can now be brought in using the regular clr.AddReference. When clr.AddReference sees a compiled assembly, it publishes the module as well. So one can simply import the module into the code.

clr.AddReference("foo.dll")
import foo

Multiple files and main

The function can take multiple python files and compile them into one dll. What if you want it to be a standalone executable? There are two things to be done. First, a stub exe is needed that can load the dll. Second, a way to distinguish the main module is needed. The keyword args that CompileModules can take comes in handy here

import clr
clr.CompileModules("foo.dll", "foo.py", "bar.py", mainModule="main.py")

Now a stub exe can be written that loads up this compiled dll. The IronPython sample pyc.py has code that does shows how to generate a stub exe.

Wait, what is -X:SaveAssemblies mode then?

When IronPython is started with -X:SaveAssemblies, it generates a dll containing IL corresponding to the code it executed. Sounds an awful lot like compilation doesn't it? The difference is one is executable IL and the other isn't.

To understand the difference, one needs to understand that IronPython under normal course of its execution generates IL anyway. Every statement is converted to the DLR AST and IL gets spit out for the ASTs which is then executed. The SaveAssemblies mode simply dumps the generated IL into a dll. It is meant as a debugging device. So what is missing from this IL that prevents it from being re-executable code? The short answer is Dynamic Sites. The sites that are generated during the execution are not persisted. The compilation feature does exactly this - it persists the dynamic sites as well. Lets look at an example here and compare the generated IL in reflector. (Only the relevant code is copied over from reflector). This python code:

print 2 + 5
print 2 * 5
print 3 / 5

when run with -X:SaveAssemblies mode produces this code:

public static CallSite<DynamicSiteTarget<int, int, object>> #Constant207;
public static CallSite<DynamicSiteTarget<int, int, object>> #Constant208;
public static CallSite<DynamicSiteTarget<int, int, object>> #Constant209;
$lineNo = 1;
PythonOps.Print(__global_context, #Constant207.Target(#Constant207, 2, 5));
$lineNo = 2;
PythonOps.Print(__global_context, #Constant208.Target(#Constant208, 2, 5));
$lineNo = 3;
PythonOps.Print(__global_context, #Constant209.Target(#Constant209, 3, 5));

Notice that the Constants defined here are actually defined as fields on the generated type and this type doesn't get instantiated anywhere and therefore the sites don't get assigned anywhere. The same python code when compiled with clr.CompiledModules produces this code:

object[] objArray = new object[] { 
CallSite<DynamicSiteTarget<int, int, object>>.Create(PythonOps.MakeOperationAction(context, "Add")), 
CallSite<DynamicSiteTarget<int, int, object>>.Create(PythonOps.MakeOperationAction(context, "Multiply")), 
CallSite<DynamicSiteTarget<int, int, object>>.Create(PythonOps.MakeOperationAction(context, "Divide")) 
};
line = 1;
PythonOps.Print(context, ((CallSite<DynamicSiteTarget<int, int, object>>)objArray[0]).Target(
    (CallSite<DynamicSiteTarget<int, int, object>>)objArray[0], 2, 5));
line = 2;
PythonOps.Print(context, ((CallSite<DynamicSiteTarget<int, int, object>>)objArray[1]).Target(
    (CallSite<DynamicSiteTarget<int, int, object>>)objArray[1], 2, 5));
line = 3;
PythonOps.Print(context, ((CallSite<DynamicSiteTarget<int, int, object>>)objArray[2]).Target(
    (CallSite<DynamicSiteTarget<int, int, object>>)objArray[2], 3, 5));

You can see that all the dynamic call sites are being created here and their targets are being invoked. This then is perfectly executable code - maybe not as succinct as the python code but it does the same thing :)

Published Wednesday, August 06, 2008 7:28 AM by srivatsn
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

Wednesday, August 06, 2008 2:36 AM by a-foton &raquo; Static Compilation of IronPython scripts

# a-foton &raquo; Static Compilation of IronPython scripts

Monday, October 20, 2008 6:11 PM by Juba17

# re: Static Compilation of IronPython scripts

Hi.

I made a small tool in Ironpython. Im new to it and when testing everything is ok.

But I've been trying to compile both scripts (program script and form class script without sucess)

I just can't get it to work has a standalone exe. I followed the example above:

import clr

clr.CompileModules("form.dll", "foo.py", "form.py", mainModule="program.py")

And I get the form.dll but then I want to get the executable and I get lost. Saw the pyc example and tried to compile with the pyc but get an error about the CompilerSink.

Could you please point more info on how to compile to exe the applications?

My version is Ironpython v. 2 beta 5

Greetings

Friday, October 24, 2008 3:03 AM by srivatsn

# re: Static Compilation of IronPython scripts

For creating a exe you have to use the pyc sample like this:

ipy.exe pyc.py /main:program.py foo.py form.py /target:winexe

I suppose this is throwing an error for you. Why don't you send a mail to me via the contact link with the exact error?

Saturday, October 25, 2008 2:30 AM by Juba17

# re: Static Compilation of IronPython scripts

Sry but can't see contact link. I will post the error:

line:

ipy.exe "D:\IronPython 2.0\Samples\Pyc\pyc.py" /main:"D:\IronPython 2.0\proyeto cod4\warning_system.py" "D:\IronPython 2.0\proyeto cod4\cod4form.py" /target:winexe

error:

Traceback (most recent call last):

 File "D:\IronPython 2.0\Samples\Pyc\pyc.py", line 35, in D:\IronPython 2.0\Sam

ples\Pyc\pyc.py

AttributeError: 'namespace#' object has no attribute 'CompilerSink'

Sunday, October 26, 2008 4:45 AM by Juba17

# re: Static Compilation of IronPython scripts

Yes I checked and pyc can't be used with 2.0 version since the only attributes available in Hosting call are:

['ErrorCodes', 'Python', 'PythonCommandLine', 'PythonConsoleOptions', 'PythonOptionsParser']

So how is the independent executable made after all?

Sunday, October 26, 2008 10:37 PM by srivatsn

# re: Static Compilation of IronPython scripts

Are you using the 1.0 pyc sample by any chance? The sample was updated for 2.0 and is here - http://www.codeplex.com/IronPython/Release/ProjectReleases.aspx?ReleaseId=14353

Monday, October 27, 2008 5:55 PM by Juba17

# re: Static Compilation of IronPython scripts

Thanks for your help ;)

I managed to compile it with pyc but I must've misunderstand the topic since when I compiled it still requires python lib and Ironpython dlls.

am I doing something wrong or it's suppose to be that way? I mean to a simple app to distribute I have to included the all of python lib? It's not pratical, I must be wrong.

Monday, October 27, 2008 6:04 PM by srivatsn

# re: Static Compilation of IronPython scripts

You will require the four IronPython DLLs for the exe to run but you can compile the python libraries along with your py files. It doesn't have to be the entire python library as well- only files that are required by your app.

Monday, October 27, 2008 8:08 PM by Juba17

# re: Static Compilation of IronPython scripts

So i need to include in the compile all the regular modules? os, sys, socket, so on? But sometimes they need other modules to run. For instance. When i compiled the last time it required me some modules and other modules those modules run. Isn't there a easy way to know wich one are the sub-modules?

Friday, October 31, 2008 1:56 PM by srivatsn

# re: Static Compilation of IronPython scripts

There might be some static dependency analysis tools for python out there. I'm not aware of any though.

Leave a Comment

(required) 
required 
(required) 
 
Page view tracker