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: