// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using IronPython.Runtime;
using IronPython.Runtime.Operations;
using IronPython.Hosting;
using IKVM.Reflection;
using IKVM.Reflection.Emit;
using System.Resources;
using Type = IKVM.Reflection.Type;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Hosting.Providers;
namespace IronPythonCompiler {
public class Program {
///
/// Generates the stub .exe file for starting the app
///
///
private static void GenerateExe(Config config) {
var u = new Universe();
var aName = new AssemblyName(Path.GetFileNameWithoutExtension(new FileInfo(config.Output).Name));
var ab = u.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Save, config.OutputPath);
var mb = ab.DefineDynamicModule(config.Output, aName.Name + (aName.Name.EndsWith(".exe") ? string.Empty : ".exe"));
var tb = mb.DefineType("PythonMain", IKVM.Reflection.TypeAttributes.Public);
if (!string.IsNullOrEmpty(config.Win32Icon)) {
ab.__DefineIconResource(File.ReadAllBytes(config.Win32Icon));
}
var attributes = new List> {
Tuple.Create(config.FileVersion, u.Import(typeof(System.Reflection.AssemblyFileVersionAttribute))),
Tuple.Create(config.ProductName, u.Import(typeof(System.Reflection.AssemblyProductAttribute))),
Tuple.Create(config.ProductVersion, u.Import(typeof(System.Reflection.AssemblyInformationalVersionAttribute))),
Tuple.Create(config.Copyright, u.Import(typeof(System.Reflection.AssemblyCopyrightAttribute)))
};
foreach (var attr in attributes) {
if (!string.IsNullOrWhiteSpace(config.FileVersion)) {
CustomAttributeBuilder builder = new CustomAttributeBuilder(attr.Item2.GetConstructor(new[] { u.Import(typeof(string)) }), new object[] { attr.Item1 });
ab.SetCustomAttribute(builder);
}
}
ab.DefineVersionInfoResource();
MethodBuilder assemblyResolveMethod = null;
ILGenerator gen = null;
if (config.Standalone) {
ConsoleOps.Info("Generating stand alone executable");
config.Embed = true;
foreach (var a in System.AppDomain.CurrentDomain.GetAssemblies()) {
var n = new AssemblyName(a.FullName);
if (!a.IsDynamic && a.EntryPoint == null && (n.Name.StartsWith("IronPython") || n.Name == "Microsoft.Dynamic" || n.Name == "Microsoft.Scripting")) {
ConsoleOps.Info($"\tEmbedded {n.Name} {n.Version}");
var f = new FileStream(a.Location, FileMode.Open, FileAccess.Read);
mb.DefineManifestResource("Dll." + n.Name, f, IKVM.Reflection.ResourceAttributes.Public);
}
}
foreach (var dll in config.DLLs) {
var name = Path.GetFileNameWithoutExtension(dll);
ConsoleOps.Info($"\tEmbedded {name}");
var f = new FileStream(dll, FileMode.Open, FileAccess.Read);
mb.DefineManifestResource("Dll." + name, f, IKVM.Reflection.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, u.Import(typeof(System.Reflection.Assembly)), new IKVM.Reflection.Type[] { u.Import(typeof(System.Object)), u.Import(typeof(System.ResolveEventArgs)) });
gen = assemblyResolveMethod.GetILGenerator();
var s = gen.DeclareLocal(u.Import(typeof(System.IO.Stream))); // resource stream
gen.Emit(OpCodes.Ldnull);
gen.Emit(OpCodes.Stloc, s);
var d = gen.DeclareLocal(u.Import(typeof(byte[]))); // data buffer;
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Reflection.Assembly)).GetMethod("GetEntryAssembly"), Type.EmptyTypes);
gen.Emit(OpCodes.Ldstr, "Dll.");
gen.Emit(OpCodes.Ldarg_1); // The event args
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.ResolveEventArgs)).GetMethod("get_Name"), Type.EmptyTypes);
gen.Emit(OpCodes.Newobj, u.Import(typeof(System.Reflection.AssemblyName)).GetConstructor(new IKVM.Reflection.Type[] { u.Import(typeof(string)) }));
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Reflection.AssemblyName)).GetMethod("get_Name"), Type.EmptyTypes);
gen.EmitCall(OpCodes.Call, u.Import(typeof(string)).GetMethod("Concat", new IKVM.Reflection.Type[] { u.Import(typeof(string)), u.Import(typeof(string)) }), Type.EmptyTypes);
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.Reflection.Assembly)).GetMethod("GetManifestResourceStream", new IKVM.Reflection.Type[] { u.Import(typeof(string)) }), Type.EmptyTypes);
gen.Emit(OpCodes.Stloc, s);
gen.Emit(OpCodes.Ldloc, s);
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.IO.Stream)).GetMethod("get_Length"), Type.EmptyTypes);
gen.Emit(OpCodes.Newarr, u.Import(typeof(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, u.Import(typeof(System.IO.Stream)).GetMethod("get_Length"), Type.EmptyTypes);
gen.Emit(OpCodes.Conv_I4);
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.IO.Stream)).GetMethod("Read", new IKVM.Reflection.Type[] { u.Import(typeof(byte[])), u.Import(typeof(int)), u.Import(typeof(int)) }), Type.EmptyTypes);
gen.Emit(OpCodes.Pop);
gen.Emit(OpCodes.Ldloc, d);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Reflection.Assembly)).GetMethod("Load", new IKVM.Reflection.Type[] { u.Import(typeof(byte[])) }), Type.EmptyTypes);
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.
var staticConstructor = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
gen = staticConstructor.GetILGenerator();
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.AppDomain)).GetMethod("get_CurrentDomain"), Type.EmptyTypes);
gen.Emit(OpCodes.Ldnull);
gen.Emit(OpCodes.Ldftn, assemblyResolveMethod);
gen.Emit(OpCodes.Newobj, u.Import(typeof(System.ResolveEventHandler)).GetConstructor(new IKVM.Reflection.Type[] { u.Import(typeof(object)), u.Import(typeof(System.IntPtr)) }));
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.AppDomain)).GetMethod("add_AssemblyResolve"), Type.EmptyTypes);
gen.Emit(OpCodes.Ret);
}
var mainMethod = tb.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, u.Import(typeof(int)), Type.EmptyTypes);
if (config.Target == PEFileKinds.WindowApplication && config.UseMta) {
mainMethod.SetCustomAttribute(u.Import(typeof(System.MTAThreadAttribute)).GetConstructor(Type.EmptyTypes), new byte[0]);
} else if (config.Target == PEFileKinds.WindowApplication) {
mainMethod.SetCustomAttribute(u.Import(typeof(System.STAThreadAttribute)).GetConstructor(Type.EmptyTypes), new byte[0]);
}
gen = mainMethod.GetILGenerator();
// variables for saving original working directory and return code of script
var strVar = gen.DeclareLocal(u.Import(typeof(string)));
var intVar = gen.DeclareLocal(u.Import(typeof(int)));
LocalBuilder dictVar = null;
if (config.PythonOptions.Count > 0) {
var True = u.Import(typeof(ScriptingRuntimeHelpers)).GetField("True");
var False = u.Import(typeof(ScriptingRuntimeHelpers)).GetField("False");
dictVar = gen.DeclareLocal(u.Import(typeof(Dictionary)));
gen.Emit(OpCodes.Newobj, u.Import(typeof(Dictionary)).GetConstructor(Type.EmptyTypes));
gen.Emit(OpCodes.Stloc, dictVar);
foreach (var option in config.PythonOptions) {
gen.Emit(OpCodes.Ldloc, dictVar);
gen.Emit(OpCodes.Ldstr, option.Key);
if (option.Value is int val) {
if (val >= -128 && val <= 127)
gen.Emit(OpCodes.Ldc_I4_S, val); // this is more optimized
else
gen.Emit(OpCodes.Ldc_I4, val);
gen.Emit(OpCodes.Box, u.Import(typeof(System.Int32)));
} else if (option.Value.Equals(ScriptingRuntimeHelpers.True)) {
gen.Emit(OpCodes.Ldsfld, True);
} else if (option.Value.Equals(ScriptingRuntimeHelpers.False)) {
gen.Emit(OpCodes.Ldsfld, False);
}
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(Dictionary)).GetMethod("Add", new IKVM.Reflection.Type[] { u.Import(typeof(string)), u.Import(typeof(object)) }), Type.EmptyTypes);
}
}
Label tryStart = gen.BeginExceptionBlock();
// get the ScriptCode assembly...
if (config.Embed) {
// put the generated DLL into the resources for the stub exe
var mem = new MemoryStream();
var rw = new ResourceWriter(mem);
rw.AddResource("IPDll." + Path.GetFileNameWithoutExtension(config.Output) + ".dll", File.ReadAllBytes(Path.Combine(config.OutputPath, config.Output) + ".dll"));
rw.Generate();
mem.Position = 0;
mb.DefineManifestResource("IPDll.resources", mem, ResourceAttributes.Public);
File.Delete(Path.Combine(config.OutputPath, config.Output) + ".dll");
// generate code to load the resource
gen.Emit(OpCodes.Ldstr, "IPDll");
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Reflection.Assembly)).GetMethod("GetEntryAssembly"), Type.EmptyTypes);
gen.Emit(OpCodes.Newobj, u.Import(typeof(System.Resources.ResourceManager)).GetConstructor(new IKVM.Reflection.Type[] { u.Import(typeof(string)), u.Import(typeof(System.Reflection.Assembly)) }));
gen.Emit(OpCodes.Ldstr, "IPDll." + Path.GetFileNameWithoutExtension(config.Output) + ".dll");
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Resources.ResourceManager)).GetMethod("GetObject", new IKVM.Reflection.Type[] { u.Import(typeof(string)) }), Type.EmptyTypes);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Reflection.Assembly)).GetMethod("Load", new IKVM.Reflection.Type[] { u.Import(typeof(byte[])) }), Type.EmptyTypes);
} else {
// save current working directory
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Environment)).GetMethod("get_CurrentDirectory"), Type.EmptyTypes);
gen.Emit(OpCodes.Stloc, strVar);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Reflection.Assembly)).GetMethod("GetEntryAssembly"), Type.EmptyTypes);
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.Reflection.Assembly)).GetMethod("get_Location"), Type.EmptyTypes);
gen.Emit(OpCodes.Newobj, u.Import(typeof(System.IO.FileInfo)).GetConstructor(new IKVM.Reflection.Type[] { u.Import(typeof(string)) }));
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.IO.FileInfo)).GetMethod("get_Directory"), Type.EmptyTypes);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.IO.DirectoryInfo)).GetMethod("get_FullName"), Type.EmptyTypes);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Environment)).GetMethod("set_CurrentDirectory"), Type.EmptyTypes);
gen.Emit(OpCodes.Ldstr, config.Output + ".dll");
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.IO.Path)).GetMethod("GetFullPath", new IKVM.Reflection.Type[] { u.Import(typeof(string)) }), Type.EmptyTypes);
// result of GetFullPath stays on the stack during the restore of the
// original working directory
// restore original working directory
gen.Emit(OpCodes.Ldloc, strVar);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Environment)).GetMethod("set_CurrentDirectory"), Type.EmptyTypes);
// 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, u.Import(typeof(System.Reflection.Assembly)).GetMethod("LoadFile", new IKVM.Reflection.Type[] { u.Import(typeof(string)) }), Type.EmptyTypes);
}
// 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
if (config.PythonOptions.Count > 0) {
gen.Emit(OpCodes.Ldloc, dictVar);
} else {
gen.Emit(OpCodes.Ldnull);
}
// call InitializeModuleEx
// (this will also run the script)
// and put the return code on the stack
gen.EmitCall(OpCodes.Call, u.Import(typeof(PythonOps)).GetMethod("InitializeModuleEx",
new IKVM.Reflection.Type[] { u.Import(typeof(System.Reflection.Assembly)), u.Import(typeof(string)), u.Import(typeof(string[])), u.Import(typeof(bool)), u.Import(typeof(Dictionary)) }),
Type.EmptyTypes);
gen.Emit(OpCodes.Stloc, intVar);
gen.BeginCatchBlock(u.Import(typeof(Exception)));
if (config.Target == PEFileKinds.ConsoleApplication) {
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.Exception)).GetMethod("get_Message", Type.EmptyTypes), Type.EmptyTypes);
gen.Emit(OpCodes.Stloc, strVar);
gen.Emit(OpCodes.Ldstr, config.ErrorMessageFormat);
gen.Emit(OpCodes.Ldloc, strVar);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Console)).GetMethod("WriteLine", new IKVM.Reflection.Type[] { u.Import(typeof(string)), u.Import(typeof(string)) }), Type.EmptyTypes);
} else {
gen.EmitCall(OpCodes.Callvirt, u.Import(typeof(System.Exception)).GetMethod("get_Message", Type.EmptyTypes), Type.EmptyTypes);
gen.Emit(OpCodes.Stloc, strVar);
gen.Emit(OpCodes.Ldstr, config.ErrorMessageFormat);
gen.Emit(OpCodes.Ldloc, strVar);
gen.EmitCall(OpCodes.Call, u.Import(typeof(string)).GetMethod("Format", new IKVM.Reflection.Type[] { u.Import(typeof(string)), u.Import(typeof(string)) }), Type.EmptyTypes);
gen.Emit(OpCodes.Ldstr, "Error");
gen.Emit(OpCodes.Ldc_I4, (int)System.Windows.Forms.MessageBoxButtons.OK);
gen.Emit(OpCodes.Ldc_I4, (int)System.Windows.Forms.MessageBoxIcon.Error);
gen.EmitCall(OpCodes.Call, u.Import(typeof(System.Windows.Forms.MessageBox)).GetMethod("Show", new IKVM.Reflection.Type[] { u.Import(typeof(string)), u.Import(typeof(string)), u.Import(typeof(System.Windows.Forms.MessageBoxButtons)), u.Import(typeof(System.Windows.Forms.MessageBoxIcon)) }), Type.EmptyTypes);
gen.Emit(OpCodes.Pop);
}
gen.Emit(OpCodes.Ldc_I4, -1); // return code is -1 to show failure
gen.Emit(OpCodes.Stloc, intVar);
gen.EndExceptionBlock();
gen.Emit(OpCodes.Ldloc, intVar);
gen.Emit(OpCodes.Ret);
tb.CreateType();
ab.SetEntryPoint(mainMethod, config.Target);
string fileName = aName.Name.EndsWith(".exe") ? aName.Name : aName.Name + ".exe";
ab.Save(fileName, config.Platform, config.Machine);
}
public static int Main(string[] args) {
var files = new List();
var config = new Config();
config.ParseArgs(args);
if (!config.Validate()) {
ConsoleOps.Usage(true);
}
// we don't use the engine, but we create it so we can have a default context.
ScriptEngine engine = Python.CreateEngine(config.PythonOptions);
ConsoleOps.Info($"IronPython Compiler for {engine.Setup.DisplayName} ({engine.LanguageVersion})");
ConsoleOps.Info($"{config}");
ConsoleOps.Info("compiling...");
var compileOptions = new Dictionary() {
{ "mainModule", config.MainName },
{ "assemblyFileVersion", config.FileVersion },
{ "copyright", config.Copyright },
{ "productName", config.ProductName },
{ "productVersion", config.ProductVersion },
};
try {
ClrModule.CompileModules(DefaultContext.DefaultCLS,
Path.Combine(config.OutputPath, Path.ChangeExtension(config.Output, ".dll")),
compileOptions,
config.Files.ToArray());
var outputfilename = Path.Combine(config.OutputPath, Path.ChangeExtension(config.Output, ".dll"));
if (config.Target != PEFileKinds.Dll) {
outputfilename = Path.Combine(config.OutputPath, Path.ChangeExtension(config.Output, ".exe"));
GenerateExe(config);
}
ConsoleOps.Info($"Saved to {outputfilename}");
} catch (Exception e) {
Console.WriteLine();
ConsoleOps.Error(true, e.Message);
}
return 0;
}
}
}