views:

222

answers:

3

What is the best way to pack up an IronPython application for deployment? After scouring the web the best thing I've come up with (and what I'm currently doing) is using clr.CompileModules() to glue together my entire project's .py files into one .dll, and then having a single run.py do this to run the dll:

import clr
clr.AddReference('compiledapp.dll')

import app

This is still suboptimal, though, because it means I have to

  1. distribute 3 files (the .dll, the .xaml, and the run.py launcher)
  2. install IronPython on the host machine

Plus, this feels so... hacky, after the wonderful integration IronPython already has with Visual Studio 2010. I'm completely mystified as to why there is no integrated build system for IPy apps, seeing as it all boils down to IL anyway.

Ideally, I want to be able to have a single .exe with the .xaml merged inside somehow (I read that C# apps compile XAML to BAML and merge them in the executable), and without requiring IronPython to be installed to run. Is this at least halfway possible? (I suppose it's ok if the exe needs some extra .DLLs with it or something. The important part is that it's in .exe form.)


Some edits to clarify: I have tried pyc.py, but it seems to not acknowledge the fact that my project is not just app.py. The size of the exe it produces suggests that it is just 'compiling' app.py without including any of the other files into the exe. So, how do I tell it to compile every file in my project?

To help visualize this, here is a screenshot of my project's solution explorer window.


Edit II: It seems that unfortunately the only way is to use pyc.py and pass every single file to it as a parameter. There are two questions I have for this approach:

  1. How do I possibly process a command line that big? There's a maximum of 256 characters in a command.
  2. How does pyc.py know to preserve the package/folder structure? As shown in my project screenshot above, how will my compiled program know to access modules that are in subfolders, such as accessing DT\Device? Is the hierarchy somehow 'preserved' in the dll?

Edit III: Since passing 70 filenames to pyc.py through the command line will be unwieldy, and in the interest of solving the problem of building IPy projects more elegantly, I've decided to augment pyc.py.

I've added code that reads in a .pyproj file through the /pyproj: parameter, parses the XML, and grabs the list of py files used in the project from there. This has been working pretty well; however, the executable produced seems to be unable to access the python subpackages (subfolders) that are part of my project. My version of pyc.py with my .pyproj reading support patch can be found here: http://pastebin.com/FgXbZY29

When this new pyc.py is run on my project, this is the output:

c:\Projects\GenScheme\GenScheme>"c:\Program Files (x86)\IronPython 2.7\ipy.exe"
pyc.py /pyproj:GenScheme.pyproj /out:App /main:app.py /target:exe
Input Files:
        c:\Projects\GenScheme\GenScheme\__init__.py
        c:\Projects\GenScheme\GenScheme\Agent.py
        c:\Projects\GenScheme\GenScheme\AIDisplay.py
        c:\Projects\GenScheme\GenScheme\app.py
        c:\Projects\GenScheme\GenScheme\BaseDevice.py
        c:\Projects\GenScheme\GenScheme\BaseManager.py
        c:\Projects\GenScheme\GenScheme\BaseSubSystem.py
        c:\Projects\GenScheme\GenScheme\ControlSchemes.py
        c:\Projects\GenScheme\GenScheme\Cu64\__init__.py
        c:\Projects\GenScheme\GenScheme\Cu64\agent.py
        c:\Projects\GenScheme\GenScheme\Cu64\aidisplays.py
        c:\Projects\GenScheme\GenScheme\Cu64\devmapper.py
        c:\Projects\GenScheme\GenScheme\Cu64\timedprocess.py
        c:\Projects\GenScheme\GenScheme\Cu64\ui.py
        c:\Projects\GenScheme\GenScheme\decorators.py
        c:\Projects\GenScheme\GenScheme\DeviceMapper.py
        c:\Projects\GenScheme\GenScheme\DT\__init__.py
        c:\Projects\GenScheme\GenScheme\DT\Device.py
        c:\Projects\GenScheme\GenScheme\DT\Manager.py
        c:\Projects\GenScheme\GenScheme\DT\SubSystem.py
        c:\Projects\GenScheme\GenScheme\excepts.py
        c:\Projects\GenScheme\GenScheme\FindName.py
        c:\Projects\GenScheme\GenScheme\GenScheme.py
        c:\Projects\GenScheme\GenScheme\PMX\__init__.py
        c:\Projects\GenScheme\GenScheme\PMX\Device.py
        c:\Projects\GenScheme\GenScheme\PMX\Manager.py
        c:\Projects\GenScheme\GenScheme\PMX\SubSystem.py
        c:\Projects\GenScheme\GenScheme\pyevent.py
        c:\Projects\GenScheme\GenScheme\Scheme.py
        c:\Projects\GenScheme\GenScheme\Simulated\__init__.py
        c:\Projects\GenScheme\GenScheme\Simulated\Device.py
        c:\Projects\GenScheme\GenScheme\Simulated\SubSystem.py
        c:\Projects\GenScheme\GenScheme\speech.py
        c:\Projects\GenScheme\GenScheme\stdoutWriter.py
        c:\Projects\GenScheme\GenScheme\Step.py
        c:\Projects\GenScheme\GenScheme\TimedProcess.py
        c:\Projects\GenScheme\GenScheme\UI.py
        c:\Projects\GenScheme\GenScheme\VirtualSubSystem.py
        c:\Projects\GenScheme\GenScheme\Waddle.py
Output:
        App
Target:
        ConsoleApplication
Platform:
        ILOnly
Machine:
        I386
Compiling...
Saved to App

So it correctly read in the list of files in the .pyproj... Great! But running the exe gives me this:

Unhandled Exception: IronPython.Runtime.Exceptions.ImportException: 
No module named Cu64.ui

So even though Cu64\ui.py is obviously included in compilation, the exe, when run, can't find it. This is what I was afraid of in point #2 in the previous edit. How do I preserve the package hierarchy of my project? Perhaps compiling each package seperately may be needed?

I'll extend the bounty for this question. Ultimately my hope is that we can get a working pyc.py that reads in pyproj files and produces working exes in one step. Then maybe it could even be submitted to IronPython's codeplex to be included in the next release... ;]

+2  A: 

It "boils down to IL", but it isn't compatible with the IL that C# code produces, so it can't be directly compiled to a standalone .exe file. You'll need to use pyc.py to compile your code to a stub EXE with the DLL that CompileModules creates. Then distribute those files with IronPython.dll, IronPython.Modules.dll, Microsoft.Dynamic.dll, Microsoft.Scripting.Debugging.dll, Microsoft.Scripting.dll, and of course the XAML file.

To compile other files, just add them as arguments: ipy.exe pyc.py /main:app.py /target:winexe another.py another2.py additional.py

jcao219
Very interesting. I'll give pyc.py another try.
Aphex
`ipy.exe pyc.py /main:app.py /target:winexe` produces a tiny 3kb app.exe that does nothing. Is there something else I need to do here?
Aphex
It also produces app.dll. Distribute that with those dlls mentioned earlier and your XAML file.
jcao219
There is indeed a small app.dll produced. Seems like its too small to be actually useful, but in the interest of trying, where can I find those 5 DLLs that I need? I'll try running it with those in the same folder.
Aphex
The 5 DLLs are either inside of `C:\Windows\Microsoft.NET\assembly\GAC_MSIL` or the IronPython installation directory, but probably the former if you are using the latest version of IronPython.
jcao219
OK, I copied the DLLs over from the GAC. Still, the exe does nothing. I'm thinking pyc.py simply isn't compiling all of my project's files.
Aphex
To compile other files, just add them as arguments: `ipy.exe pyc.py /main:app.py /target:winexe another.py another2.py additional.py`
jcao219
+1  A: 

Use pyc.py to produce app.exe and don't forget to include app.dll and IronPython libraries.

As for XAML - I've created project just for .xaml files that I compile in VS and then use them from IronPython. For example:

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="/CompiledStyle;component/Style.xaml" /> 
</ResourceDictionary.MergedDictionaries> 
Lukas Cenovsky
Okay, I've wrestled with pyc.py and I still dont understand how to make it use all of the files I need. My project consists of 78 files over 5 folders and is launched by running app.py; however as mentioned in my comment to jcao219 running `ipy.exe pyc.py /main:app.py /target:winexe` produces a useless app.exe that does nothing when launched.
Aphex
I recommend you to start with simple hello world app. Trying to make work 78 files is too much. Start with one file, that add an another one, then subfolder, etc. Also does your app run when you start it by simple: `ipy.exe app.py`?
Lukas Cenovsky
Yes, `ipy.exe app.py` runs great. By the way, I added a screenshot of my current project structure to the first post... I'm beginning to think I need to give ALL of my files to pyc.py as commandline parameters. Is this **really** the way I'm supposed to build and distribute my IPy apps? Type out `ipy.exe pyc.py app.py <78 .py files here> /main:app.py /target:winexe`?
Aphex
Yes, that's how. Or you can write some script that does it for you. IronPython's support for building like this is not as great as C#, since C# already fills that niche.
jcao219
jcao219 is right - you have to give `pyc.py` all files you want to compile. Also note there is a bug in `pyc.py` that forces to create console app when you specify `/main`. Just drop line 97 and it should work.
Lukas Cenovsky
I was afraid of that. I guess i could write a build script that queries my project folder for every py file and passes them to pyc... I only have two questions. See my edited original post.
Aphex
+1  A: 

To create a set of assemblies for your IronPython application so that you can distribute it you can either use pyc.py or SharpDevelop.

To compile using pyc.py:

ipy.exe pyc.py /main:Program.py Form.py File1.py File2.py ... /target:winexe

Given the amount of files in your project you could try using SharpDevelop instead of maintaining a long command line for pyc.py. You will need to create a new IronPython project in SharpDevelop and import your files into the project. You will probably need to import the files one at a time since SharpDevelop lacks a way to import multiple files unless they are in a subfolder.

You can then use SharpDevelop to compile your application into an executable and a dll. All the other required files, such as IronPython.dll, Microsoft.Scripting.dll, will be in the bin/debug or bin/release folder. SharpDevelop uses clr.CompileModules and a custom MSBuild task behind the scenes to generate the binaries.

Any IronPython packages defined in your project should be usable from your application after compilation.

Packaging up the XAML can be done by embedding the xaml as a resource. Then using code similar to the following:

import clr

clr.AddReference('PresentationFramework')
clr.AddReference('System')

from System.IO import FileMode, FileStream, Path
from System.Reflection import Assembly
from System.Windows import Application
from System.Windows.Markup import XamlReader

executingAssemblyFileName = Assembly.GetEntryAssembly().Location
directory = Path.GetDirectoryName(executingAssemblyFileName)
xamlFileName = Path.Combine(directory, "Window1.xaml")

stream = FileStream(xamlFileName, FileMode.Open)
window = XamlReader.Load(stream)
app = Application()
app.Run(window)

SharpDevelop 3.2 does not embed resource files correctly so you will need to use SharpDevelop 4.

If you are using IronPython 2.7 you can use the new clr.LoadComponent method that takes an object and either a XAML filename or stream and wires up that object to the XAML.

Whilst the C# compiler can compile your XAML into a BAML resource doing the same with IronPython has a few problems. If you do not link the XAML to a class via the x:Class attribute then it is possible to compile the XAML into a BAML resource and have that embedded into your assembly. However you will not get any autogenerated code so you will need to create that code yourself. Another problem is that this will not work out of the box with SharpDevelop. You will need to edit the SharpDevelop.Build.Python.targets file and change the from Python to C#. Trying to use the x:Class attribute will not work since the BAML reader cannot access any associated IronPython class. This is because the generated IL in the compiled IronPython application is very different to that in a C# or VB.NET assembly.

Matt Ward