From 94f8ab6b21d36267300447015444e1a96f9a1e9e Mon Sep 17 00:00:00 2001
From: slide <slide.o.mix@gmail.com>
Date: Thu, 23 Feb 2012 20:49:45 -0700
Subject: [PATCH] Fix 32288 and 26706

- Add option to pyc /standalone to embed the IronPython assemblies into the stub exe
  - Added catch of ArgumentException in sys.GetPrefix to handle when there is no .Location
- Added check for IRONPYTHONPATH in the InitializeModule method
---
 Languages/IronPython/IronPython/Modules/sys.cs     |    2 +
 .../IronPython/Runtime/Operations/PythonOps.cs     |   14 ++++-
 Languages/IronPython/Public/Tools/Scripts/pyc.py   |   77 +++++++++++++++++---
 3 files changed, 83 insertions(+), 10 deletions(-)

diff --git a/Languages/IronPython/IronPython/Modules/sys.cs b/Languages/IronPython/IronPython/Modules/sys.cs
index 038c3b0..72a9223 100644
--- a/Languages/IronPython/IronPython/Modules/sys.cs
+++ b/Languages/IronPython/IronPython/Modules/sys.cs
@@ -56,6 +56,8 @@ namespace IronPython.Modules {
                 prefix = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
             } catch (SecurityException) {
                 prefix = String.Empty;
+            } catch (ArgumentException) {
+                prefix = String.Empty;
             }
 #else
             prefix = String.Empty;
diff --git a/Languages/IronPython/IronPython/Runtime/Operations/PythonOps.cs b/Languages/IronPython/IronPython/Runtime/Operations/PythonOps.cs
index 84e4b84..f31dc00 100644
--- a/Languages/IronPython/IronPython/Runtime/Operations/PythonOps.cs
+++ b/Languages/IronPython/IronPython/Runtime/Operations/PythonOps.cs
@@ -3696,7 +3696,7 @@ namespace IronPython.Runtime.Operations {
         /// does the actual work of adding references and importing the main module.  Upon completion it returns
         /// the exit code that the program reported via SystemExit or 0.
         /// </summary>
-        public static int InitializeModule(Assembly/*!*/ precompiled, string/*!*/ main, string[] references) {
+        public static int InitializeModule(Assembly/*!*/ precompiled, string/*!*/ main, string[] references, bool ignoreEnvVars=false) {
             ContractUtils.RequiresNotNull(precompiled, "precompiled");
             ContractUtils.RequiresNotNull(main, "main");
 
@@ -3707,6 +3707,18 @@ namespace IronPython.Runtime.Operations {
 
             var pythonContext = (PythonContext)HostingHelpers.GetLanguageContext(pythonEngine);
 
+            if (!ignoreEnvVars) {
+                int pathIndex = pythonContext.PythonOptions.SearchPaths.Count;
+                string path = Environment.GetEnvironmentVariable("IRONPYTHONPATH");
+                if (path != null && path.Length > 0) {
+                    string[] paths = path.Split(Path.PathSeparator);
+                    foreach (string p in paths) {
+                        pythonContext.InsertIntoPath(pathIndex++, p);
+                    }
+                }
+
+                // TODO: add more environment variable setup here...
+            }
 
             foreach (var scriptCode in SavableScriptCode.LoadFromAssembly(pythonContext.DomainManager, precompiled)) {
                 pythonContext.GetCompiledLoader().AddScriptCode(scriptCode);
diff --git a/Languages/IronPython/Public/Tools/Scripts/pyc.py b/Languages/IronPython/Public/Tools/Scripts/pyc.py
index 028da9d..33750c3 100644
--- a/Languages/IronPython/Public/Tools/Scripts/pyc.py
+++ b/Languages/IronPython/Public/Tools/Scripts/pyc.py
@@ -30,7 +30,7 @@ EXE/WinEXE specific options:
     /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
@@ -46,14 +46,70 @@ 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
+from System.Reflection import AssemblyName, TypeAttributes, MethodAttributes, ResourceAttributes, CallingConventions
 
-def GenerateExe(name, targetKind, platform, machine, main_module, embed=False):
+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():
+            if not a.IsDynamic and not a.GlobalAssemblyCache and not a.EntryPoint:
+                n = AssemblyName(a.FullName)
+                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](()))
@@ -61,7 +117,7 @@ def GenerateExe(name, targetKind, platform, machine, main_module, embed=False):
 
     # get the ScriptCode assembly...
     if embed:
-	# put the generated DLL into the resources for the stub exe
+        # 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")
@@ -100,15 +156,14 @@ def GenerateExe(name, targetKind, platform, machine, main_module, embed=False):
         gen.EmitCall(OpCodes.Call, clr.GetClrType(System.Reflection.Assembly).GetMethod("LoadFile", (clr.GetClrType(str), )), ())
 
     # emit module name
-    gen.Emit(OpCodes.Ldstr, "__main__")
-    gen.Emit(OpCodes.Ldnull)
+    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("InitializeModule"), ())
-
     gen.Emit(OpCodes.Ret)
-
     tb.CreateType()
     ab.SetEntryPoint(mainMethod, targetKind)
     ab.Save(aName.Name + ".exe", platform, machine)
@@ -122,6 +177,7 @@ def Main(args):
     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:"):
@@ -154,6 +210,9 @@ def Main(args):
         elif arg.startswith("/embed"):
             embed = True
 
+        elif arg.startswith("/standalone"):
+            standalone = True
+
         elif arg in ["/?", "-?", "/h", "-h"]:
             print __doc__
             sys.exit(0)
@@ -188,7 +247,7 @@ def Main(args):
     clr.CompileModules(output + ".dll", mainModule = main_name, *files)
 
     if target != System.Reflection.Emit.PEFileKinds.Dll:
-        GenerateExe(output, target, platform, machine, main_name, embed)
+        GenerateExe(output, target, platform, machine, main_name, embed, standalone)
 
     print "Saved to %s" % (output, )
 
-- 
1.7.6.msysgit.0

