Skip to content
This repository has been archived by the owner on Feb 15, 2019. It is now read-only.

Commit

Permalink
Fully functional compiled to EXE IronPython support is now working!
Browse files Browse the repository at this point in the history
  • Loading branch information
Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) committed Mar 7, 2012
1 parent c5e4117 commit 14dabd5
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 6 deletions.
55 changes: 55 additions & 0 deletions BEXML/BEXML.pyproj
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{acaefc7d-2e27-4e17-8184-89468d9edfb1}</ProjectGuid>
<ProjectHome>.</ProjectHome>
<StartupFile>tests\TestParseBErepoWithHTTP.py</StartupFile>
<SearchPath>
</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<InterpreterId>fcc291aa-427c-498c-a4d7-4502d6449b8c</InterpreterId>
<LaunchProvider>IronPython (.NET) launcher</LaunchProvider>
<InterpreterVersion>2.7</InterpreterVersion>
<OutputPath>.</OutputPath>
<Name>BEXML</Name>
<RootNamespace>BEXML</RootNamespace>
<CommandLineArguments />
<InterpreterPath />
<InterpreterArguments />
<DebugStdLib>False</DebugStdLib>
<IsWindowsApplication>False</IsWindowsApplication>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<ItemGroup>
<Folder Include="libBEXML\" />
<Folder Include="libBEXML\parsers\" />
<Folder Include="tests\" />
</ItemGroup>
<ItemGroup>
<Compile Include="bexmlsrv.py" />
<Compile Include="libBEXML\coerce_datetime.py" />
<Compile Include="libBEXML\comment.py" />
<Compile Include="libBEXML\issue.py" />
<Compile Include="libBEXML\parserbase.py" />
<Compile Include="libBEXML\parsers\be_dir.py" />
<Compile Include="libBEXML\parsers\__init__.py" />
<Compile Include="libBEXML\propertieddictionary.py" />
<Compile Include="libBEXML\__init__.py" />
<Compile Include="setup.py" />
<Compile Include="tests\TestParseBErepoWithHTTP.py" />
<Compile Include="tests\TestParseBErepoWithLib.py" />
</ItemGroup>
<ItemGroup>
<Content Include="tests\bugs.bugseverywhere.org.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
</Project>
75 changes: 75 additions & 0 deletions BEXML/ironpython/CompileToStandalone.py
@@ -0,0 +1,75 @@
#!/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)
260 changes: 260 additions & 0 deletions BEXML/ironpython/pyc.py
@@ -0,0 +1,260 @@
#####################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# This source code is subject to terms and conditions of the Apache License, Version 2.0. A
# copy of the license can be found in the License.html file at the root of this distribution. If
# you cannot locate the Apache License, Version 2.0, please send an email to
# ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
# by the terms of the Apache License, Version 2.0.
#
# You must not remove this notice, or any other, from this software.
#
#
#####################################################################################

"""
pyc: The Command-Line Python Compiler
Usage: ipy.exe pyc.py [options] file [file ...]
Options:
/out:output_file Output file name (default is main_file.<extenstion>)
/target:dll Compile only into dll. Default
/target:exe Generate console executable stub for startup in addition to dll.
/target:winexe Generate windows executable stub for startup in addition to dll.
/files:input_file Read to read files from, one per line.
/? /h This message
EXE/WinEXE specific options:
/main:main_file.py Main file of the project (module to be executed first)
/platform:x86 Compile for x86 only
/platform:x64 Compile for x64 only
/embed Embeds the generated DLL as a resource into the executable which is loaded at runtime
/standalone Embeds the IronPython assemblies into the stub executable.
Example:
ipy.exe pyc.py /main:Program.py Form.py /target:winexe
"""

import sys
import clr
clr.AddReferenceByPartialName("IronPython")

from System.Collections.Generic import List
import IronPython.Hosting as Hosting
from IronPython.Runtime.Operations import PythonOps
import System
from System.Reflection import Emit, Assembly
from System.Reflection.Emit import OpCodes, AssemblyBuilderAccess
from System.Reflection import AssemblyName, TypeAttributes, MethodAttributes, ResourceAttributes, CallingConventions

def GenerateExe(name, targetKind, platform, machine, main_module, embed, standalone):
"""generates the stub .EXE file for starting the app"""
aName = AssemblyName(System.IO.FileInfo(name).Name)
ab = PythonOps.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave)
mb = ab.DefineDynamicModule(name, aName.Name + ".exe")
tb = mb.DefineType("PythonMain", TypeAttributes.Public)
assemblyResolveMethod = None

if standalone:
print "Generating stand alone executable"
embed = True

for a in System.AppDomain.CurrentDomain.GetAssemblies():
n = AssemblyName(a.FullName)
if not a.IsDynamic and not a.EntryPoint and (n.Name.StartsWith("IronPython") or n.Name in ['Microsoft.Dynamic', 'Microsoft.Scripting']):
print "\tEmbedding %s %s" % (n.Name, str(n.Version))
f = System.IO.FileStream(a.Location, System.IO.FileMode.Open, System.IO.FileAccess.Read)
mb.DefineManifestResource("Dll." + n.Name, f, ResourceAttributes.Public)

# we currently do no error checking on what is passed in to the assemblyresolve event handler
assemblyResolveMethod = tb.DefineMethod("AssemblyResolve", MethodAttributes.Public | MethodAttributes.Static, clr.GetClrType(Assembly), (clr.GetClrType(System.Object), clr.GetClrType(System.ResolveEventArgs)))
gen = assemblyResolveMethod.GetILGenerator()
s = gen.DeclareLocal(clr.GetClrType(System.IO.Stream)) # resource stream
gen.Emit(OpCodes.Ldnull)
gen.Emit(OpCodes.Stloc, s)
d = gen.DeclareLocal(clr.GetClrType(System.Array[System.Byte])) # data buffer
gen.EmitCall(OpCodes.Call, clr.GetClrType(Assembly).GetMethod("GetEntryAssembly"), ())
gen.Emit(OpCodes.Ldstr, "Dll.")
gen.Emit(OpCodes.Ldarg_1) # The event args
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(System.ResolveEventArgs).GetMethod("get_Name"), ())
gen.Emit(OpCodes.Newobj, clr.GetClrType(AssemblyName).GetConstructor((str, )))
gen.EmitCall(OpCodes.Call, clr.GetClrType(AssemblyName).GetMethod("get_Name"), ())
gen.EmitCall(OpCodes.Call, clr.GetClrType(str).GetMethod("Concat", (str, str)), ())
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(Assembly).GetMethod("GetManifestResourceStream", (str, )), ())
gen.Emit(OpCodes.Stloc, s)
gen.Emit(OpCodes.Ldloc, s)
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(System.IO.Stream).GetMethod("get_Length"), ())
gen.Emit(OpCodes.Newarr, clr.GetClrType(System.Byte))
gen.Emit(OpCodes.Stloc, d)
gen.Emit(OpCodes.Ldloc, s)
gen.Emit(OpCodes.Ldloc, d)
gen.Emit(OpCodes.Ldc_I4_0)
gen.Emit(OpCodes.Ldloc, s)
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(System.IO.Stream).GetMethod("get_Length"), ())
gen.Emit(OpCodes.Conv_I4)
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(System.IO.Stream).GetMethod("Read", (clr.GetClrType(System.Array[System.Byte]), int, int)), ())
gen.Emit(OpCodes.Pop)
gen.Emit(OpCodes.Ldloc, d)
gen.EmitCall(OpCodes.Call, clr.GetClrType(Assembly).GetMethod("Load", (clr.GetClrType(System.Array[System.Byte]), )), ())
gen.Emit(OpCodes.Ret)

# generate a static constructor to assign the AssemblyResolve handler (otherwise it tries to use IronPython before it adds the handler)
# the other way of handling this would be to move the call to InitializeModule into a separate method.
staticConstructor = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, System.Type.EmptyTypes)
gen = staticConstructor.GetILGenerator()
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.AppDomain).GetMethod("get_CurrentDomain"), ())
gen.Emit(OpCodes.Ldnull)
gen.Emit(OpCodes.Ldftn, assemblyResolveMethod)
gen.Emit(OpCodes.Newobj, clr.GetClrType(System.ResolveEventHandler).GetConstructor((clr.GetClrType(System.Object), clr.GetClrType(System.IntPtr))))
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(System.AppDomain).GetMethod("add_AssemblyResolve"), ())
gen.Emit(OpCodes.Ret)

mainMethod = tb.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, int, ())
if targetKind == System.Reflection.Emit.PEFileKinds.WindowApplication:
mainMethod.SetCustomAttribute(clr.GetClrType(System.STAThreadAttribute).GetConstructor(()), System.Array[System.Byte](()))
gen = mainMethod.GetILGenerator()

# get the ScriptCode assembly...
if embed:
# put the generated DLL into the resources for the stub exe
w = mb.DefineResource("IPDll.resources", "Embedded IronPython Generated DLL")
w.AddResource("IPDll." + name, System.IO.File.ReadAllBytes(name + ".dll"))
System.IO.File.Delete(name + ".dll")

# generate code to load the resource
gen.Emit(OpCodes.Ldstr, "IPDll")
gen.EmitCall(OpCodes.Call, clr.GetClrType(Assembly).GetMethod("GetEntryAssembly"), ())
gen.Emit(OpCodes.Newobj, clr.GetClrType(System.Resources.ResourceManager).GetConstructor((str, clr.GetClrType(Assembly))))
gen.Emit(OpCodes.Ldstr, "IPDll." + name)
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Resources.ResourceManager).GetMethod("GetObject", (str, )), ())
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Reflection.Assembly).GetMethod("Load", (clr.GetClrType(System.Array[System.Byte]), )), ())
else:
# variables for saving original working directory und return code of script
wdSave = gen.DeclareLocal(str)

# save current working directory
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Environment).GetMethod("get_CurrentDirectory"), ())
gen.Emit(OpCodes.Stloc, wdSave)
gen.EmitCall(OpCodes.Call, clr.GetClrType(Assembly).GetMethod("GetEntryAssembly"), ())
gen.EmitCall(OpCodes.Callvirt, clr.GetClrType(Assembly).GetMethod("get_Location"), ())
gen.Emit(OpCodes.Newobj, clr.GetClrType(System.IO.FileInfo).GetConstructor( (str, ) ))
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.IO.FileInfo).GetMethod("get_Directory"), ())
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.IO.DirectoryInfo).GetMethod("get_FullName"), ())
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Environment).GetMethod("set_CurrentDirectory"), ())
gen.Emit(OpCodes.Ldstr, name + ".dll")
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.IO.Path).GetMethod("GetFullPath", (clr.GetClrType(str), )), ())
# result of GetFullPath stays on the stack during the restore of the
# original working directory

# restore original working directory
gen.Emit(OpCodes.Ldloc, wdSave)
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Environment).GetMethod("set_CurrentDirectory"), ())

# for the LoadFile() call, the full path of the assembly is still is on the stack
# as the result from the call to GetFullPath()
gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Reflection.Assembly).GetMethod("LoadFile", (clr.GetClrType(str), )), ())

# emit module name
gen.Emit(OpCodes.Ldstr, "__main__") # main module name
gen.Emit(OpCodes.Ldnull) # no references
gen.Emit(OpCodes.Ldc_I4_0) # don't ignore environment variables for engine startup

# call InitializeModule
# (this will also run the script)
gen.EmitCall(OpCodes.Call, clr.GetClrType(PythonOps).GetMethod("InitializeModuleEx"), ())
gen.Emit(OpCodes.Ret)
tb.CreateType()
ab.SetEntryPoint(mainMethod, targetKind)
ab.Save(aName.Name + ".exe", platform, machine)

def Main(args):
files = []
main = None # The main file to start the execution (passed to the PythonCompiler)
main_name = None # File which will drive the name of the assembly if "output" not provided
output = None # Output assembly name
target = System.Reflection.Emit.PEFileKinds.Dll
platform = System.Reflection.PortableExecutableKinds.ILOnly
machine = System.Reflection.ImageFileMachine.I386
embed = False # True to embed the generated DLL into the executable
standalone = False # True to embed all the IronPython and Microsoft.Scripting DLL's into the generated exe

for arg in args:
if arg.startswith("/main:"):
main_name = main = arg[6:]
# only override the target kind if its current a DLL
if target == System.Reflection.Emit.PEFileKinds.Dll:
target = System.Reflection.Emit.PEFileKinds.ConsoleApplication

elif arg.startswith("/out:"):
output = arg[5:]

elif arg.startswith("/target:"):
tgt = arg[8:]
if tgt == "exe": target = System.Reflection.Emit.PEFileKinds.ConsoleApplication
elif tgt == "winexe": target = System.Reflection.Emit.PEFileKinds.WindowApplication
else: target = System.Reflection.Emit.PEFileKinds.Dll

elif arg.startswith("/platform:"):
pform = arg[10:]
if pform == "x86":
platform = System.Reflection.PortableExecutableKinds.ILOnly | System.Reflection.PortableExecutableKinds.Required32Bit
machine = System.Reflection.ImageFileMachine.I386
elif pform == "x64":
platform = System.Reflection.PortableExecutableKinds.ILOnly | System.Reflection.PortableExecutableKinds.PE32Plus
machine = System.Reflection.ImageFileMachine.AMD64
else:
platform = System.Reflection.PortableExecutableKinds.ILOnly
machine = System.Reflection.ImageFileMachine.I386

elif arg.startswith("/embed"):
embed = True

elif arg.startswith("/standalone"):
standalone = True

elif arg.startswith("/files:"):
with open(arg[7:], 'r') as ih:
files+=[x[:-1] for x in ih.readlines()]

elif arg in ["/?", "-?", "/h", "-h"]:
print __doc__
sys.exit(0)

else:
files.append(arg)

if not files and not main_name:
print __doc__
sys.exit(0)

if target != System.Reflection.Emit.PEFileKinds.Dll and main_name == None:
print __doc__
sys.exit(0)
print "EXEs require /main:<filename> to be specified"

if not output and main_name:
output = System.IO.Path.GetFileNameWithoutExtension(main_name)
elif not output and files:
output = System.IO.Path.GetFileNameWithoutExtension(files[0])

print "Input Files:"
for file in files:
print "\t%s" % file

print "Output:\n\t%s" % output
print "Target:\n\t%s" % target
print "Platform:\n\t%s" % platform
print "Machine:\n\t%s" % machine

print "Compiling..."
clr.CompileModules(output + ".dll", mainModule = main_name, *files)

if target != System.Reflection.Emit.PEFileKinds.Dll:
GenerateExe(output, target, platform, machine, main_name, embed, standalone)

print "Saved to %s" % (output, )

if __name__ == "__main__":
Main(sys.argv[1:])

0 comments on commit 14dabd5

Please sign in to comment.