diff --git a/Src/executables/IronPython.Compiler/IconInjector.cs b/Src/executables/IronPython.Compiler/IconInjector.cs new file mode 100644 index 000000000..8c7f2e5dc --- /dev/null +++ b/Src/executables/IronPython.Compiler/IconInjector.cs @@ -0,0 +1,202 @@ +// 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.Runtime.InteropServices; + +namespace IronPython.Compiler { + + /// + /// Fork from . + /// + internal static class IconInjector { + + private const nint RT_ICON = 3; + private const nint RT_GROUP_ICON = 14; + + public static void InjectIcon(string exeFileName, string iconFileName, nint iconGroupID = 1, ushort iconBaseID = 1) { + IconFile iconFile = IconFile.FromFile(iconFileName); + nint hUpdate = BeginUpdateResourceW(exeFileName, false); + byte[] data = iconFile.CreateIconGroupData(iconBaseID); + UpdateResourceW(hUpdate, RT_GROUP_ICON, iconGroupID, 0, data, data.Length); + for (int i = 0; i <= iconFile.ImageCount - 1; i++) { + byte[] image = iconFile[i]; + UpdateResourceW(hUpdate, RT_ICON, iconBaseID + i, 0, image, image.Length); + } + EndUpdateResourceW(hUpdate, false); + } + + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern nint BeginUpdateResourceW([In] string fileName, [In][MarshalAs(UnmanagedType.Bool)] bool deleteExistingResources); + + [DllImport("kernel32")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool UpdateResourceW([In] nint hUpdate, [In] nint type, [In] nint name, [In] short language, [In, Optional][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] byte[] data, [In] int dataSize); + + [DllImport("kernel32")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EndUpdateResourceW([In] nint hUpdate, [In][MarshalAs(UnmanagedType.Bool)] bool discard); + + // The first structure in an ICO file lets us know how many images are in the file. + [StructLayout(LayoutKind.Sequential)] + private struct ICONDIR { + // Reserved, must be 0 + public ushort Reserved; + // Resource type, 1 for icons. + public ushort Type; + // How many images. + public ushort Count; + // The native structure has an array of ICONDIRENTRYs as a final field. + } + + // Each ICONDIRENTRY describes one icon stored in the ico file. The offset says where the icon image data + // starts in the file. The other fields give the information required to turn that image data into a valid + // bitmap. + [StructLayout(LayoutKind.Sequential)] + private struct ICONDIRENTRY { + /// + /// The width, in pixels, of the image. + /// + public byte Width; + /// + /// The height, in pixels, of the image. + /// + public byte Height; + /// + /// The number of colors in the image; (0 if >= 8bpp) + /// + public byte ColorCount; + /// + /// Reserved (must be 0). + /// + public byte Reserved; + /// + /// Color planes. + /// + public ushort Planes; + /// + /// Bits per pixel. + /// + public ushort BitCount; + /// + /// The length, in bytes, of the pixel data. + /// + public int BytesInRes; + /// + /// The offset in the file where the pixel data starts. + /// + public int ImageOffset; + } + + // Each image is stored in the file as an ICONIMAGE structure: + //typdef struct + //{ + // BITMAPINFOHEADER icHeader; // DIB header + // RGBQUAD icColors[1]; // Color table + // BYTE icXOR[1]; // DIB bits for XOR mask + // BYTE icAND[1]; // DIB bits for AND mask + //} ICONIMAGE, *LPICONIMAGE; + + + [StructLayout(LayoutKind.Sequential)] + private struct BITMAPINFOHEADER { + public uint Size; + public int Width; + public int Height; + public ushort Planes; + public ushort BitCount; + public uint Compression; + public uint SizeImage; + public int XPelsPerMeter; + public int YPelsPerMeter; + public uint ClrUsed; + public uint ClrImportant; + } + + // The icon in an exe/dll file is stored in a very similar structure: + [StructLayout(LayoutKind.Sequential, Pack = 2)] + private struct GRPICONDIRENTRY { + public byte Width; + public byte Height; + public byte ColorCount; + public byte Reserved; + public ushort Planes; + public ushort BitCount; + public int BytesInRes; + public ushort ID; + } + + private class IconFile { + private ICONDIR iconDir; + private ICONDIRENTRY[] iconEntry; + + private byte[][] iconImage; + + public ushort ImageCount => iconDir.Count; + + public byte[] this[int index] => iconImage[index]; + + public static unsafe IconFile FromFile(string filename) { + IconFile instance = new IconFile(); + // Read all the bytes from the file. + byte[] fileBytes = System.IO.File.ReadAllBytes(filename); + // First struct is an ICONDIR + // Pin the bytes from the file in memory so that we can read them. + // If we didn't pin them then they could move around (e.g. when the + // garbage collector compacts the heap) + fixed (byte* pinnedBytes = fileBytes) { + ICONDIR* iconDirs = (ICONDIR*)pinnedBytes; + // Read the ICONDIR + instance.iconDir = iconDirs[0]; + // which tells us how many images are in the ico file. For each image, there's a ICONDIRENTRY, and associated pixel data. + instance.iconEntry = new ICONDIRENTRY[instance.iconDir.Count]; + instance.iconImage = new byte[instance.iconDir.Count][]; + // The first ICONDIRENTRY will be immediately after the ICONDIR, so the offset to it is the size of ICONDIR + ICONDIRENTRY* array = (ICONDIRENTRY*)(iconDirs + 1); + // After reading an ICONDIRENTRY we step forward by the size of an ICONDIRENTRY + for (int i = 0; i < instance.iconDir.Count; i++) { + // Grab the structure. + ICONDIRENTRY entry = array[i]; + instance.iconEntry[i] = entry; + // Grab the associated pixel data. + byte[] image = new byte[entry.BytesInRes]; + Buffer.BlockCopy(fileBytes, entry.ImageOffset, image, 0, entry.BytesInRes); + instance.iconImage[i] = image; + } + } + return instance; + } + + public unsafe byte[] CreateIconGroupData(ushort iconBaseID) { + // This will store the memory version of the icon. + int sizeOfIconGroupData = sizeof(ICONDIR) + + sizeof(GRPICONDIRENTRY) * ImageCount; + byte[] data = new byte[sizeOfIconGroupData]; + fixed (byte* pinnedData = data) { + ICONDIR* iconDirs = (ICONDIR*)pinnedData; + iconDirs[0] = iconDir; + GRPICONDIRENTRY* array = (GRPICONDIRENTRY*)(iconDirs + 1); + for (ushort i = 0; i < ImageCount; i++) { + byte[] image = iconImage[i]; + fixed (byte* pinnedBitmapInfoHeader = image) { + BITMAPINFOHEADER bitmapHeader = *(BITMAPINFOHEADER*)pinnedBitmapInfoHeader; + GRPICONDIRENTRY grpEntry = new GRPICONDIRENTRY { + Width = iconEntry[i].Width, + Height = iconEntry[i].Height, + ColorCount = iconEntry[i].ColorCount, + Reserved = iconEntry[i].Reserved, + Planes = bitmapHeader.Planes, + BitCount = bitmapHeader.BitCount, + BytesInRes = iconEntry[i].BytesInRes, + ID = checked((ushort)(iconBaseID + i)) + }; + array[i] = grpEntry; + } + } + } + return data; + } + } + } +} diff --git a/src/core/IronPython/Runtime/ClrModule.cs b/src/core/IronPython/Runtime/ClrModule.cs index ea4605735..c872b6477 100644 --- a/src/core/IronPython/Runtime/ClrModule.cs +++ b/src/core/IronPython/Runtime/ClrModule.cs @@ -15,6 +15,10 @@ using ComTypeLibDesc = Microsoft.Scripting.ComInterop.ComTypeLibDesc; #endif +#if FEATURE_FILESYSTEM && FEATURE_REFEMIT +using System.Reflection.Emit; +#endif + using System; using System.Collections; using System.Collections.Generic; @@ -942,6 +946,196 @@ public static void CompileModules(CodeContext/*!*/ context, string/*!*/ assembly SavableScriptCode.SaveToAssembly(assemblyName, kwArgs, code.ToArray()); } + + public static AssemblyGen CreateAssemblyGen(CodeContext/*!*/ context, string/*!*/ assemblyName, [ParamDictionary]IDictionary kwArgs, [NotNone] params string/*!*/[]/*!*/ filenames) { + ContractUtils.RequiresNotNull(assemblyName, nameof(assemblyName)); + ContractUtils.RequiresNotNullItems(filenames, nameof(filenames)); + + PythonContext pc = context.LanguageContext; + + for (int i = 0; i < filenames.Length; i++) { + filenames[i] = Path.GetFullPath(filenames[i]); + } + + Dictionary packageMap = BuildPackageMap(filenames); + + List code = new List(); + foreach (string filename in filenames) { + if (!pc.DomainManager.Platform.FileExists(filename)) { + throw PythonOps.IOError($"Couldn't find file for compilation: {filename}"); + } + + ScriptCode sc; + + string modName; + string dname = Path.GetDirectoryName(filename); + string outFilename = ""; + if (Path.GetFileName(filename) == "__init__.py") { + // remove __init__.py to get package name + dname = Path.GetDirectoryName(dname); + if (String.IsNullOrEmpty(dname)) { + modName = Path.GetDirectoryName(filename); + } else { + modName = Path.GetFileNameWithoutExtension(Path.GetDirectoryName(filename)); + } + outFilename = Path.DirectorySeparatorChar + "__init__.py"; + } else { + modName = Path.GetFileNameWithoutExtension(filename); + } + + // see if we have a parent package, if so incorporate it into + // our name + if (packageMap.TryGetValue(dname, out string parentPackage)) { + modName = parentPackage + "." + modName; + } + + outFilename = modName.Replace('.', Path.DirectorySeparatorChar) + outFilename; + + SourceUnit su = pc.CreateSourceUnit( + new FileStreamContentProvider( + context.LanguageContext.DomainManager.Platform, + filename + ), + outFilename, + pc.DefaultEncoding, + SourceCodeKind.File + ); + + sc = context.LanguageContext.GetScriptCode(su, modName, ModuleOptions.Initialize, Compiler.CompilationMode.ToDisk); + + code.Add((SavableScriptCode)sc); + } + + if (kwArgs != null && kwArgs.TryGetValue("mainModule", out object mainModule)) { + if (mainModule is string strModule) { + if (!pc.DomainManager.Platform.FileExists(strModule)) { + throw PythonOps.IOError("Couldn't find main file for compilation: {0}", strModule); + } + + SourceUnit su = pc.CreateFileUnit(strModule, pc.DefaultEncoding, SourceCodeKind.File); + code.Add((SavableScriptCode)context.LanguageContext.GetScriptCode(su, "__main__", ModuleOptions.Initialize, Compiler.CompilationMode.ToDisk)); + } + } + + return CreateAssemblyGen(assemblyName, kwArgs, code.ToArray()); + } + + private class CodeInfo { + public readonly MethodBuilder Builder; + public readonly ScriptCode Code; + public readonly Type DelegateType; + + public CodeInfo(MethodBuilder builder, ScriptCode code, Type delegateType) { + Builder = builder; + Code = code; + DelegateType = delegateType; + } + } + + private static AssemblyGen CreateAssemblyGen(string assemblyName, IDictionary assemblyAttributes, params SavableScriptCode[] codes) { + ContractUtils.RequiresNotNull(assemblyName, nameof(assemblyName)); + ContractUtils.RequiresNotNullItems(codes, nameof(codes)); + + // break the assemblyName into it's dir/name/extension + string dir = Path.GetDirectoryName(assemblyName); + if (string.IsNullOrEmpty(dir)) { + dir = Environment.CurrentDirectory; + } + + string name = Path.GetFileNameWithoutExtension(assemblyName); + string ext = Path.GetExtension(assemblyName); + + // build the assembly & type gen that all the script codes will live in... + AssemblyGen ag = new AssemblyGen(new AssemblyName(name), dir, ext, /*emitSymbols*/false, assemblyAttributes); + TypeBuilder tb = ag.DefinePublicType("DLRCachedCode", typeof(object), true); + TypeGen tg = new TypeGen(ag, tb); + // then compile all of the code + + MethodInfo compileForSave = + typeof(SavableScriptCode).GetMethod( + "CompileForSave", + BindingFlags.Instance | BindingFlags.NonPublic, + Type.DefaultBinder, + new[] { typeof(TypeGen) }, + null); + + Dictionary> langCtxBuilders = new Dictionary>(); + foreach (SavableScriptCode sc in codes) { + if (!langCtxBuilders.TryGetValue(sc.LanguageContext.GetType(), out List builders)) { + langCtxBuilders[sc.LanguageContext.GetType()] = builders = new List(); + } + KeyValuePair compInfo = (KeyValuePair)compileForSave.Invoke(sc, new[] { tg }); + + builders.Add(new CodeInfo(compInfo.Key, sc, compInfo.Value)); + } + + MethodBuilder mb = tb.DefineMethod( + "GetScriptCodeInfo", + MethodAttributes.SpecialName | MethodAttributes.Public | MethodAttributes.Static, + typeof(MutableTuple), + ReflectionUtils.EmptyTypes); + + ILGen ilgen = new ILGen(mb.GetILGenerator()); + + var langsWithBuilders = langCtxBuilders.ToArray(); + + // lang ctx array + ilgen.EmitArray(typeof(Type), langsWithBuilders.Length, (index) => { + ilgen.Emit(OpCodes.Ldtoken, langsWithBuilders[index].Key); + ilgen.EmitCall(typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), new[] { typeof(RuntimeTypeHandle) })); + }); + + // builders array of array + ilgen.EmitArray(typeof(Delegate[]), langsWithBuilders.Length, (index) => { + List builders = langsWithBuilders[index].Value; + + ilgen.EmitArray(typeof(Delegate), builders.Count, (innerIndex) => { + ilgen.EmitNull(); + ilgen.Emit(OpCodes.Ldftn, builders[innerIndex].Builder); + ilgen.EmitNew( + builders[innerIndex].DelegateType, + new[] { typeof(object), typeof(IntPtr) } + ); + }); + }); + + // paths array of array + ilgen.EmitArray(typeof(string[]), langsWithBuilders.Length, (index) => { + List builders = langsWithBuilders[index].Value; + + ilgen.EmitArray(typeof(string), builders.Count, (innerIndex) => { + ilgen.EmitString(builders[innerIndex].Code.SourceUnit.Path); + }); + }); + + // 4th element in tuple - custom per-language data + ilgen.EmitArray(typeof(string[]), langsWithBuilders.Length, (index) => { + List builders = langsWithBuilders[index].Value; + + ilgen.EmitArray(typeof(string), builders.Count, (innerIndex) => { + ICustomScriptCodeData data = builders[innerIndex].Code as ICustomScriptCodeData; + if (data != null) { + ilgen.EmitString(data.GetCustomScriptCodeData()); + } else { + ilgen.Emit(OpCodes.Ldnull); + } + }); + }); + + ilgen.EmitNew( + typeof(MutableTuple), + new[] { typeof(Type[]), typeof(Delegate[][]), typeof(string[][]), typeof(string[][]) } + ); + ilgen.Emit(OpCodes.Ret); + + mb.SetCustomAttribute(new CustomAttributeBuilder( + typeof(DlrCachedCodeAttribute).GetConstructor(ReflectionUtils.EmptyTypes), + ArrayUtils.EmptyObjects + )); + + tg.FinishType(); + return ag; + } #endif #if FEATURE_REFEMIT diff --git a/src/executables/IronPython.Compiler/Config.cs b/src/executables/IronPython.Compiler/Config.cs index 59600f799..2b7605c4d 100644 --- a/src/executables/IronPython.Compiler/Config.cs +++ b/src/executables/IronPython.Compiler/Config.cs @@ -1,14 +1,15 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.ComponentModel; using System.IO; +using System.Reflection; +using System.Reflection.Emit; using System.Text; -using IKVM.Reflection.Emit; - using Microsoft.Scripting.Runtime; namespace IronPython.Compiler { @@ -17,8 +18,8 @@ public class Config { public Config() { Embed = false; Files = new List(); - Platform = IKVM.Reflection.PortableExecutableKinds.ILOnly; - Machine = IKVM.Reflection.ImageFileMachine.AMD64; + Platform = PortableExecutableKinds.ILOnly; + Machine = ImageFileMachine.I386; Standalone = false; Target = PEFileKinds.Dll; UseMta = false; @@ -106,6 +107,11 @@ public bool Standalone { private set; } + public bool NoLogo { + get; + private set; + } + public List Files { get; private set; @@ -126,12 +132,12 @@ public bool Embed { internal set; } - public IKVM.Reflection.ImageFileMachine Machine { + public ImageFileMachine Machine { get; private set; } - public IKVM.Reflection.PortableExecutableKinds Platform { + public PortableExecutableKinds Platform { get; private set; } @@ -154,7 +160,7 @@ public void ParseArgs(IEnumerable args, List respFiles = null) { } else if (arg.StartsWith("/out:", StringComparison.Ordinal)) { Output = arg.Substring(5).Trim('"'); } else if (arg.StartsWith("/target:", StringComparison.Ordinal)) { - string tgt = arg.Substring(8).Trim('"'); + string tgt = arg.Substring(8).Trim('"').ToLowerInvariant(); switch (tgt) { case "exe": Target = PEFileKinds.ConsoleApplication; @@ -162,24 +168,51 @@ public void ParseArgs(IEnumerable args, List respFiles = null) { case "winexe": Target = PEFileKinds.WindowApplication; break; + case "library": + case "dll": default: Target = PEFileKinds.Dll; break; } } else if (arg.StartsWith("/platform:", StringComparison.Ordinal)) { - string plat = arg.Substring(10).Trim('"'); + string plat = arg.Substring(10).Trim('"').ToLowerInvariant(); switch (plat) { case "x86": - Platform = IKVM.Reflection.PortableExecutableKinds.ILOnly | IKVM.Reflection.PortableExecutableKinds.Required32Bit; - Machine = IKVM.Reflection.ImageFileMachine.I386; + case "i386": + Platform = PortableExecutableKinds.ILOnly | PortableExecutableKinds.Required32Bit; + Machine = ImageFileMachine.I386; break; case "x64": - Platform = IKVM.Reflection.PortableExecutableKinds.ILOnly | IKVM.Reflection.PortableExecutableKinds.PE32Plus; - Machine = IKVM.Reflection.ImageFileMachine.AMD64; + case "amd64": + Platform = PortableExecutableKinds.ILOnly | PortableExecutableKinds.PE32Plus; + Machine = ImageFileMachine.AMD64; + break; + case "ia64": + case "itanium": + Platform = PortableExecutableKinds.ILOnly; + Machine = ImageFileMachine.IA64; + break; + case "arm": + case "arm32": + Platform = PortableExecutableKinds.ILOnly; + Machine = ImageFileMachine.ARM; + break; + case "arm64": + Platform = PortableExecutableKinds.ILOnly; + Machine = (ImageFileMachine)0xAA64; + break; + case "anycpu": + Platform = PortableExecutableKinds.ILOnly; + Machine = ImageFileMachine.I386; break; default: - Platform = IKVM.Reflection.PortableExecutableKinds.ILOnly; - Machine = IKVM.Reflection.ImageFileMachine.AMD64; + try { + int machine = (int)new Int32Converter().ConvertFromInvariantString(plat); + Platform = PortableExecutableKinds.ILOnly; + Machine = (ImageFileMachine)machine; + } catch { + goto case "anycpu"; + } break; } } else if (arg.StartsWith("/win32icon:", StringComparison.Ordinal)) { @@ -210,6 +243,8 @@ public void ParseArgs(IEnumerable args, List respFiles = null) { foreach (var f in Directory.EnumerateFiles(Environment.CurrentDirectory, pattern)) { Files.Add(Path.GetFullPath(f)); } + } else if (arg.Equals("/nologo", StringComparison.Ordinal)) { + NoLogo = true; } else if (Array.IndexOf(helpStrings, arg) >= 0) { ConsoleOps.Usage(true); } else if (arg.StartsWith("/py:", StringComparison.Ordinal)) { @@ -306,12 +341,25 @@ public bool Validate() { return false; } - if (string.IsNullOrWhiteSpace(Output) && !string.IsNullOrWhiteSpace(MainName)) { - Output = Path.GetFileNameWithoutExtension(MainName); - OutputPath = Path.GetDirectoryName(MainName); - } else if (string.IsNullOrWhiteSpace(Output) && Files != null && Files.Count > 0) { - Output = Path.GetFileNameWithoutExtension(Files[0]); - OutputPath = Path.GetDirectoryName(Files[0]); + if (string.IsNullOrWhiteSpace(Output)) { + if (!string.IsNullOrWhiteSpace(MainName)) { + Output = Path.GetFileNameWithoutExtension(MainName); + OutputPath = Path.GetDirectoryName(MainName); + } else if (Files != null && Files.Count > 0) { + Output = Path.GetFileNameWithoutExtension(Files[0]); + OutputPath = Path.GetDirectoryName(Files[0]); + } + } + else { + OutputPath = Path.GetDirectoryName(Output); + Output = Path.GetFileName(Output); + if (string.IsNullOrWhiteSpace(Output)) { + if (!string.IsNullOrWhiteSpace(MainName)) { + Output = Path.GetFileNameWithoutExtension(MainName); + } else if (Files != null && Files.Count > 0) { + Output = Path.GetFileNameWithoutExtension(Files[0]); + } + } } if (!string.IsNullOrWhiteSpace(Win32Icon) && Target == PEFileKinds.Dll) { @@ -334,7 +382,7 @@ public override string ToString() { res.AppendLine($"Output:\n\t{Output}"); res.AppendLine($"OutputPath:\n\t{OutputPath}"); res.AppendLine($"Target:\n\t{Target}"); - res.AppendLine($"Platform:\n\t{Machine}"); + res.AppendLine($"Platform:\n\t{Machine} ({Platform})"); if (Target == PEFileKinds.WindowApplication) { res.AppendLine("Threading:"); if (UseMta) { diff --git a/src/executables/IronPython.Compiler/ConsoleOps.cs b/src/executables/IronPython.Compiler/ConsoleOps.cs index eea8940e4..0be6a3d76 100644 --- a/src/executables/IronPython.Compiler/ConsoleOps.cs +++ b/src/executables/IronPython.Compiler/ConsoleOps.cs @@ -7,6 +7,8 @@ namespace IronPython.Compiler { public static class ConsoleOps { + public static bool NoLogo { get; set; } + public static void Error(string format, params object[] args) { Error(false, format, args); } @@ -34,6 +36,7 @@ public static void Warning(string format, params object[] args) { } public static void Info(string format, params object[] args) { + if (NoLogo) { return; } Console.WriteLine(format, args); } @@ -52,6 +55,7 @@ public static void Usage(bool doExit = false) { /productname: Sets the product name attribute for the generated assembly /productversion: Sets the product version attribute for the generated assembly /py: