Compiling an IronPython WPF project to exe
Asked Answered
H

5

23

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... ;]

Heathcote answered 22/10, 2010 at 17:43 Comment(5)
Command line length is 8191 characters (support.microsoft.com/kb/830473). clr.CompileModules handles packages well - it recognizes them via __init__.py as is usual for Python packages.Dug
Yes, you can use clr.CompileModules and then make a stub .exe that loads the .dll. I think there is a LoadAssembly function somewhere that can be used in C#.Mithridatism
Ah - I meant that pyc.py uses clr.CompileModules which handles packages well. So just use pyc.py and it should work ;-)Dug
I know this is really quite old, but I require to do the same! Any luck on what works?Lolitaloll
From what I remember, I ended up deploying the entire repo at once, unfortunately. However you're welcome to try my version of pyc (and try to improve it further!)Heathcote
M
5

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

Mithridatism answered 22/10, 2010 at 19:18 Comment(7)
Very interesting. I'll give pyc.py another try.Heathcote
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?Heathcote
It also produces app.dll. Distribute that with those dlls mentioned earlier and your XAML file.Mithridatism
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.Heathcote
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.Mithridatism
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.Heathcote
To compile other files, just add them as arguments: ipy.exe pyc.py /main:app.py /target:winexe another.py another2.py additional.pyMithridatism
D
5

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> 
Dug answered 23/10, 2010 at 18:5 Comment(6)
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.Heathcote
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?Dug
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?Heathcote
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.Mithridatism
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.Dug
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.Heathcote
L
5

I posted a Python script which can take an IronPython file, figure out its dependencies and compile the lot into a standalone binary at Ironpython 2.6 .py -> .exe. Hope you find it useful. It ought to work for WPF too as it bundles WPF support.

Lodged answered 7/3, 2012 at 21:14 Comment(0)
G
3

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.

Gunsmith answered 31/10, 2010 at 20:6 Comment(1)
Thanks for the writeup about SharpDevelop. It could be something to try, though I am loathe to move from Visual Studio 2010 to a different IDE. Check my recent edit for some info on what I've been doing to get pyc to read all my files; I'm really close to getting it to work.Heathcote
F
2

I installed Visual Studio 2015 with PTVS (ironpython 2.7). I created a very simple WPF project and wasn't able to compile an exe. I always got the exception "ImportError: No module named wpf".

import clr
clr.AddReferenceToFileAndPath("c:\\path\\to\\IronPython.Wpf.dll")
clr.AddReferenceToFileAndPath('c:\\path\\to\\PresentationCore.dll')
clr.AddReferenceToFileAndPath('c:\\path\\to\\PresentationFramework.dll')
clr.AddReferenceToFileAndPath('c:\\path\\to\\WindowsBase.dll')

from System.Windows import Application, Window

import wpf

class MyWindow(Window):
    def __init__(self):
        wpf.LoadComponent(self, 'RegExTester.xaml')

    def OnSearch(self, sender, e):
        self.tbOut.Text = "hello world"

if __name__ == '__main__':
    Application().Run(MyWindow())

The fault I got was because the clr clause must be before the import wpf. Steps to compile it:

  1. install pip for CPython 2.7 (not ironpython!)

  2. install ipy2asm

python -m pip install ironpycompiler

  1. compile the application like

ipy2asm compile -t winexe -e -s program.py

Farrow answered 18/12, 2015 at 19:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.