Ironpython 2.6 .py -> .exe

22,277

Solution 1

You can use pyc.py, the Python Command-Line Compiler, which is included in IronPython since version 2.6 to compile a Python script to an executable. You find it at %IRONPYTONINSTALLDIR%\Tools\Scripts\pyc.py on your hard disk.

Example

Let's assume you have a simple script test.py that just prints out something to console. You can turn this into an executable with the following command-line (assuming that the IronPython directory is the current directory and that test.py is in there, too):

ipy.exe Tools\Scripts\pyc.py /main:test.py /target:exe

Note: If you are using forms and don't want a console window to open, you want to use /target:winexe instead of /target:exe.

The result will be two files, test.dll and test.exe. test.dll will contain your actual script code, while test.exe is just a launcher for test.dll. You can distribute this EXE and DLL to other computers which do not have IronPython installed if you include the files

  • IronPython.dll,
  • Microsoft.Dynamic.dll,
  • Microsoft.Scripting.Core.dll,
  • Microsoft.Scripting.Debugging.dll,
  • Microsoft.Scripting.dll,
  • Microsoft.Scripting.ExtensionAttribute.dll and
  • IronPython.Modules.dll (sometimes needed).

Also see the blog entry IronPython - how to compile exe.

Solution 2

This is a long standing question about which there is very little information on the internet. The only known solution I can find is at http://community.sharpdevelop.net/blogs/mattward/archive/2010/03/16/CompilingPythonPackagesWithIronPython.aspx which uses SharpDevelop. However, this solution is impractical because any semi-complex python project will do a LOT of module imports, and the SharpDevelop solution requires you to create a project per import. I started at it and gave up after about thirty new projects, better to write an automated solution!

So here's my solution, and I'll warn you right now it's not being released as a proper project for good reason:

#!/usr/bin/env python
# CompileToStandalone, a Python to .NET ILR compiler which produces standalone binaries
# (C) 2012 Niall Douglas http://www.nedproductions.biz/
# Created: March 2012

import modulefinder, sys, os, subprocess, _winreg

if len(sys.argv)<2:
    print("Usage: CompileEverythingToILR.py <source py> [-outdir=<dest dir>]")
    sys.exit(0)

if sys.platform=="cli":
    print("ERROR: IronPython's ModuleFinder currently doesn't work, so run me under CPython please")
    sys.exit(1)

sourcepath=sys.argv[1]
destpath=sys.argv[2][8:] if len(sys.argv)==3 else os.path.dirname(sys.argv[0])
ironpythonpath=None
try:
    try:
        keyh=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\IronPython\\2.7\\InstallPath")
        ironpythonpath=_winreg.QueryValue(keyh, None)
    except Exception as e:
        try:
            keyh=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Wow6432Node\\IronPython\\2.7\\InstallPath")
            ironpythonpath=_winreg.QueryValue(keyh, "")
        except Exception as e:
            pass
finally:
    if ironpythonpath is not None:
        _winreg.CloseKey(keyh)
        print("IronPython found at "+ironpythonpath)
    else:
        raise Exception("Cannot find IronPython in the registry")

# What we do now is to load the python source but against the customised IronPython runtime
# library which has been hacked to work with IronPython. This spits out the right set of
# modules mostly, but we include the main python's site-packages in order to resolve any
# third party packages
print("Scanning '"+sourcepath+"' for dependencies and outputting into '"+destpath+"' ...")
searchpaths=[".", ironpythonpath+os.sep+"Lib"]
searchpaths+=[x for x in sys.path if 'site-packages' in x]
finder=modulefinder.ModuleFinder(searchpaths)
finder.run_script(sourcepath)
print(finder.report())
modules=[]
badmodules=finder.badmodules.keys()
for name, mod in finder.modules.iteritems():
    path=mod.__file__
    # Ignore internal modules
    if path is None: continue
    # Ignore DLL internal modules
    #if '\\DLLs\\' in path: continue
    # Watch out for C modules
    if os.path.splitext(path)[1]=='.pyd':
        print("WARNING: I don't support handling C modules at '"+path+"'")
        badmodules.append(name)
        continue
    modules.append((name, os.path.abspath(path)))
modules.sort()
print("Modules not imported due to not found, error or being a C module:")
print("\n".join(badmodules))
raw_input("\nPress Return if you are happy with these missing modules ...")

with open(destpath+os.sep+"files.txt", "w") as oh:
    oh.writelines([x[1]+'\n' for x in modules])
cmd='ipy64 '+destpath+os.sep+'pyc.py /main:"'+os.path.abspath(sourcepath)+'" /out:'+os.path.splitext(os.path.basename(sourcepath))[0]+' /target:exe /standalone /platform:x86 /files:'+destpath+os.sep+'files.txt'
print(cmd)
cwd=os.getcwd()
try:
    os.chdir(destpath)
    retcode=subprocess.call(cmd, shell=True)
finally:
    os.chdir(cwd)
sys.exit(retcode)

This was written against IronPython v2.7.2 RC1 using its new standalone binary feature, and indeed it does work. You get a standalone .exe file which is totally self-contained - it needs nothing else installed. The script works by parsing the imports for the supplied script and sending the entire lot to pyc.py. That's the good news.

The bad news is as follows:

  1. IronPython v2.7.2 RC1's ModuleFinder doesn't appear to work, so the above script needs to be run using CPython. It then uses CPython's ModuleFinder but against IronPython's customised runtime library. Yeah, I'm amazed it works as well ...
  2. The binaries output start at about 8Mb. A simple unit test weighed in at 16Mb. There's a lot of stuff that doesn't need to be in there e.g. it throws it Wpf support and a bit more, but still they aren't small.
  3. Load times are much slower than non-standalone. Think about forty seconds for a standalone unit test on a fast Intel Core 2 versus about three seconds for non-standalone. If compiled just for x86, that drops to ten seconds.
  4. Run time performance is slower than non-standalone by about 40%. If compiled just for x86, performance approximately doubles. This is why I left in the /platform:x86 above.
  5. There is a well known bug in CPython's encodings and codecs support where ModuleFinder doesn't include any codec support at all unless you manually specify it. So, for example, if you are using UTF-8 with codecs.open() then you NEED a "from encodings import utf_8 as some_unique_identifier" to force the dependency.
  6. The above assumes a modified pyc.py which can take a /files parameter as the command line length limit easily gets exceeded. You can modify your own pyc.py trivially, if not I've submitted the enhancement for inclusion into the next IronPython.

So there you go. It works, but the solution still needs a lot more maturing. Best of luck!

Solution 3

Check out the IronPython Samples Page

About half way down the page:

Pyc - Python Command-Line Compiler This sample shows developers how to create .NET executables directly out of IronPython scripts. The readme.htm in the download will get you started.

IronPython’s Hosting APIs can be used to compile Python scripts into DLLs, console executables, or Windows executables. The pyc.py script included in this tutorial leverages these hosting APIs and can be used to compile other Python scripts. It provides a variety of flags such as the ability to specify the target platform of the .NET assembly (e.g., x64).

While the assemblies produced by the IronPython Hosting APIs are true .NET assemblies, the dynamic nature of the Python language makes it difficult to use these from other .NET languages. In short, this means that attempting to import Python types into other .NET languages such as C# is not recommended.

Edit: Just noticed that you mentioned PYC was out of date. What makes it so? The IronPython crew seem to still be promoting it, so I would imagine that it's not that far gone.

Solution 4

I had a bit of trouble trying to implement this solution. This is what I did:

  1. Download pyc from here. This took me more searching than it should have because it seems that pyc is hard to find (and I think, a little out of date)
  2. I extracted the pyc folder from the zip file and added it to my IronPython folder in C:\Program Files
  3. Now I tried running this command on the windows console, as instructed by the readme in the pyc download: ipy.exe pyc.py other_hw.py /main:console_hw.py

It gave me this error:

Traceback (most recent call last):
  File "pyc\pyc.py", line 35, in pyc\pyc.py
AttributeError: attribute 'CompilerSink' of 'namespace#' object is read-only

I made the following change to line 35:

Before: class PycSink(Hosting.CompilerSink):

After: class PycSink():

Saving the file proved to be a problem due to permissions, so I copied the contents of pyc.py into a new IDLE window (to create a copy), deleted the existing copy of pyc.py and saved the copy as pyc.py in the same location. This takes care of permissions issues and allows changes.

After making this change, I tried running the this command again:

ipy.exe pyc.py other_hw.py /main:console_hw.py

However, this time, I got the following error:

Traceback (most recent call last):
  File "pyc\pyc.py", line 170, in pyc\pyc.py
  File "pyc\pyc.py", line 56, in Main
AttributeError: attribute 'ResourceFile' of 'namespace#' object is read-only

At this point, I took stock of the fact that it is now 1 AM and I have a midterm tomorrow, so I undid the changes and shut it down.

Please let me know if you have a solution, or any advancements on mine.

Solution 5

Yes I have found it too difficult to compile an exe so I have switched back to using standard Python. They should give a good tutorial on it on the IronPython site

Share:
22,277
user177215
Author by

user177215

Updated on July 13, 2022

Comments

  • user177215
    user177215 almost 2 years

    I already attempted using py2exe (not compatible with ipy) and PYC (out of date). Can anyone point me in the direction of a good compiler?