diff --git a/src/IKVM.MSBuild.Tasks/IkvmToolTaskDiagnosticWriter.cs b/src/IKVM.MSBuild.Tasks/IkvmToolTaskDiagnosticWriter.cs index 80ec27e7fb..8627feacef 100644 --- a/src/IKVM.MSBuild.Tasks/IkvmToolTaskDiagnosticWriter.cs +++ b/src/IKVM.MSBuild.Tasks/IkvmToolTaskDiagnosticWriter.cs @@ -4,6 +4,8 @@ using IKVM.Tools.Runner; +using Microsoft.Build.Framework; + namespace IKVM.MSBuild.Tasks { @@ -38,24 +40,73 @@ public Task ReceiveAsync(IkvmToolDiagnosticEvent @event) if (@event == null) return Task.CompletedTask; + ( + IkvmToolDiagnosticEventLevel Level, + string Code, + string Message + ) structuredLog; + +#pragma warning disable IDE0079 // Unused suppression (net472 doesn't produce IDE0057) +#pragma warning disable IDE0057 // Suppress net6 & net8 analyzer for range-operations + // unspecified format: "Level: Message" + // MSBuild Format: "Level IKVMC0000: Message" + // Some write """ + // warning IKVMC0000: Message + // (additional information) + // """ + // Skip these: + // - StdErr is mapped to Information + // - StdOut is mapped to Debug. + if (@event.Message.Length > 0 && @event.Message[0] is char first + && !char.IsWhiteSpace(first) + && @event.Message.IndexOf(": ", 0, 19) is { } firstColon and not -1) + { + structuredLog.Message = @event.Message.Substring(firstColon + 2); + + int levelLength; + if (@event.Message.IndexOf("IKVMC", 0, firstColon, StringComparison.OrdinalIgnoreCase) is { } codeIndex and not -1) + { + levelLength = codeIndex - 1; + structuredLog.Code = @event.Message.Substring(codeIndex, 9 /* IKVMC0000 */); + } + else + { + levelLength = firstColon; + structuredLog.Code = ""; + } + + structuredLog.Level = @event.Message.Substring(0, levelLength).ToUpperInvariant() switch + { + "ERROR" => IkvmToolDiagnosticEventLevel.Error, + "WARNING" => IkvmToolDiagnosticEventLevel.Warning, + _ => IkvmToolDiagnosticEventLevel.Information + }; + } + else + { + // Can't figure out level. + structuredLog = (@event.Level, null, @event.Message); + } +#pragma warning restore + try { - switch (@event.Level) + switch (structuredLog.Level) { case IkvmToolDiagnosticEventLevel.Debug: - logger.LogMessage(Microsoft.Build.Framework.MessageImportance.Low, @event.Message, @event.MessageArgs); + logger.LogMessage(null, structuredLog.Code, null, null, 0, 0, 0, 0, MessageImportance.Low, structuredLog.Message, @event.MessageArgs); writer?.WriteLine("DEBUG: " + @event.Message, @event.MessageArgs); break; case IkvmToolDiagnosticEventLevel.Information: - logger.LogMessage(Microsoft.Build.Framework.MessageImportance.Normal, @event.Message, @event.MessageArgs); + logger.LogMessage(null, structuredLog.Code, null, null, 0, 0, 0, 0, MessageImportance.Normal, structuredLog.Message, @event.MessageArgs); writer?.WriteLine("INFO: " + @event.Message, @event.MessageArgs); break; case IkvmToolDiagnosticEventLevel.Warning: - logger.LogWarning(@event.Message, @event.MessageArgs); + logger.LogWarning(null, structuredLog.Code, null, null, 0, 0, 0, 0, structuredLog.Message, @event.MessageArgs); writer?.WriteLine("WARN: " + @event.Message, @event.MessageArgs); break; case IkvmToolDiagnosticEventLevel.Error: - logger.LogWarning(@event.Message, @event.MessageArgs); + logger.LogError(null, structuredLog.Code, null, null, 0, 0, 0, 0, structuredLog.Message, @event.MessageArgs); writer?.WriteLine("ERROR: " + @event.Message, @event.MessageArgs); break; } diff --git a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets index 1f028d0d66..f95c9e7460 100644 --- a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets +++ b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.NoTasks.targets @@ -277,6 +277,9 @@ Value = b.ToString(); <_IkvmCompilerArgs Include="-nologo" /> <_IkvmCompilerArgs Include="-bootstrap" Condition=" '$(Bootstrap)' == 'true' " /> <_IkvmCompilerArgs Include="-debug:$(DebugType)" Condition=" '$(DebugType)' != 'none' " /> + <_IkvmCompilerArgs Include="-nowarn:$(NoWarn.Replace(';', ','))" Condition=" '$(NoWarn)' != '' " /> + <_IkvmCompilerArgs Include="-warnaserror" Condition=" '$(TreatWarningsAsErrors)' == 'true' " /> + <_IkvmCompilerArgs Include="-warnaserror:$(WarningsAsErrors.Replace(';', ','))" Condition=" '$(TreatWarningsAsErrors)' != 'true' And '$(WarningsAsErrors)' != '' " /> <_IkvmCompilerArgs Include="-assembly:$(AssemblyName)" /> <_IkvmCompilerArgs Include="-version:$(AssemblyVersion)" /> <_IkvmCompilerArgs Include="-runtime:$(IkvmRuntimeAssembly)" /> diff --git a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets index affb1f803e..27482bacbd 100644 --- a/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets +++ b/src/IKVM.NET.Sdk/targets/IKVM.Java.Core.Tasks.targets @@ -126,6 +126,9 @@ Platform="$(PlatformTarget.ToLowerInvariant())" Main="$(StartupObject)" Debug="$(DebugType)" + NoWarn="$(NoWarn.Replace(',', ';'))" + WarnAsError="$(TreatWarningsAsErrors)" + WarnAsErrorWarnings="$(WarningsAsErrors.Replace(',', ';'))" KeyFile="$(KeyOriginatorFile)" CompressResources="$(CompressResources)" ClassLoader="$(ClassLoader)" diff --git a/src/IKVM.Tools.Importer/CompilerClassLoader.cs b/src/IKVM.Tools.Importer/CompilerClassLoader.cs index b03d8b4388..3a33231011 100644 --- a/src/IKVM.Tools.Importer/CompilerClassLoader.cs +++ b/src/IKVM.Tools.Importer/CompilerClassLoader.cs @@ -3415,8 +3415,8 @@ sealed class CompilerOptions internal uint fileAlignment; internal bool highentropyva; internal List sharedclassloader; // should *not* be deep copied in Copy(), because we want the list of all compilers that share a class loader - internal Dictionary suppressWarnings = new Dictionary(); - internal Dictionary errorWarnings = new Dictionary(); // treat specific warnings as errors + internal HashSet suppressWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); + internal HashSet errorWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); internal bool warnaserror; // treat all warnings as errors internal FileInfo writeSuppressWarningsFile; internal List proxies = new List(); @@ -3438,8 +3438,8 @@ internal CompilerOptions Copy() { copy.externalResources = new Dictionary(externalResources); } - copy.suppressWarnings = new Dictionary(suppressWarnings); - copy.errorWarnings = new Dictionary(errorWarnings); + copy.suppressWarnings = new(suppressWarnings, StringComparer.OrdinalIgnoreCase); + copy.errorWarnings = new(errorWarnings, StringComparer.OrdinalIgnoreCase); return copy; } diff --git a/src/IKVM.Tools.Importer/IkvmImporterInternal.cs b/src/IKVM.Tools.Importer/IkvmImporterInternal.cs index 96da26085b..935be11fea 100644 --- a/src/IKVM.Tools.Importer/IkvmImporterInternal.cs +++ b/src/IKVM.Tools.Importer/IkvmImporterInternal.cs @@ -748,15 +748,7 @@ void ContinueParseCommandLine(RuntimeContext context, StaticCompiler compiler, I } else if (s.StartsWith("-nowarn:")) { - foreach (var w in s.Substring(8).Split(',')) - { - // lame way to chop off the leading zeroes - string ws = w; - while (ws.StartsWith("0")) - ws = ws.Substring(1); - - options.suppressWarnings[ws] = ws; - } + HandleWarnArg(options.suppressWarnings, s.Substring(8)); } else if (s == "-warnaserror") { @@ -764,15 +756,7 @@ void ContinueParseCommandLine(RuntimeContext context, StaticCompiler compiler, I } else if (s.StartsWith("-warnaserror:")) { - foreach (string w in s.Substring(13).Split(',')) - { - // lame way to chop off the leading zeroes - string ws = w; - while (ws.StartsWith("0")) - ws = ws.Substring(1); - - options.errorWarnings[ws] = ws; - } + HandleWarnArg(options.errorWarnings, s.Substring(13)); } else if (s.StartsWith("-runtime:")) { @@ -1446,10 +1430,11 @@ internal static void IssueMessage(StaticCompiler compiler, CompilerOptions optio return; } - string key = ((int)msgId).ToString(); + var msgIdKey = $"{(int)msgId}"; + string key = msgIdKey; for (int i = 0; ; i++) { - if (options.suppressWarnings.ContainsKey(key)) + if (options.suppressWarnings.Contains(key)) { return; } @@ -1457,9 +1442,9 @@ internal static void IssueMessage(StaticCompiler compiler, CompilerOptions optio { break; } - key += ":" + values[i]; + key = $"{key}:{values[i]}"; } - options.suppressWarnings.Add(key, key); + options.suppressWarnings.Add(key); if (options.writeSuppressWarningsFile != null) { File.AppendAllText(options.writeSuppressWarningsFile.FullName, "-nowarn:" + key + Environment.NewLine); @@ -1665,8 +1650,8 @@ internal static void IssueMessage(StaticCompiler compiler, CompilerOptions optio } bool error = msgId >= Message.StartErrors || (options.warnaserror && msgId >= Message.StartWarnings) - || options.errorWarnings.ContainsKey(key) - || options.errorWarnings.ContainsKey(((int)msgId).ToString()); + || options.errorWarnings.Contains(key) + || options.errorWarnings.Contains(msgIdKey); Console.Error.Write("{0} IKVMC{1:D4}: ", error ? "error" : msgId < Message.StartWarnings ? "note" : "warning", (int)msgId); if (error && Message.StartWarnings <= msgId && msgId < Message.StartErrors) { @@ -1686,6 +1671,41 @@ internal static void IssueMessage(StaticCompiler compiler, CompilerOptions optio } } + internal static void HandleWarnArg(ICollection target, string arg) + { + foreach (var w in arg.Split(',')) + { + // Strip IKVMC prefix + int prefixStart = w.StartsWith("IKVMC", StringComparison.OrdinalIgnoreCase) ? 5 : 0; + int contextIndex = w.IndexOf(':', prefixStart); + string context = string.Empty; + string parse; + if(contextIndex != -1) + { + // context includes ':' separator + context = w.Substring(contextIndex); + parse = w.Substring(prefixStart, contextIndex - prefixStart); + } + else + { + parse = w.Substring(prefixStart); + } + + if (!int.TryParse(parse, out var intResult)) + { + if (!Enum.TryParse(parse, out var namedResult)) + { + continue; // silently continue + } + + // Warnings are handled as int. + intResult = (int)namedResult; + } + + // Check IssueMessage + target.Add($"{intResult}{context}"); + } + } } } diff --git a/src/IKVM.Tools.Importer/StaticCompiler.cs b/src/IKVM.Tools.Importer/StaticCompiler.cs index bfc78180af..ea2460cae3 100644 --- a/src/IKVM.Tools.Importer/StaticCompiler.cs +++ b/src/IKVM.Tools.Importer/StaticCompiler.cs @@ -230,7 +230,7 @@ internal void IssueMissingTypeMessage(Type type) internal void SuppressWarning(CompilerOptions options, Message message, string name) { - options.suppressWarnings[(int)message + ":" + name] = null; + options.suppressWarnings.Add($"{(int)message}:{name}"); } internal void IssueMessage(Message msgId, params string[] values) @@ -240,250 +240,7 @@ internal void IssueMessage(Message msgId, params string[] values) internal void IssueMessage(CompilerOptions options, Message msgId, params string[] values) { - if (errorCount != 0 && msgId < Message.StartErrors && !options.warnaserror) - { - // don't display any warnings after we've emitted an error message - return; - } - - string key = ((int)msgId).ToString(); - for (int i = 0; ; i++) - { - if (options.suppressWarnings.ContainsKey(key)) - { - return; - } - if (i == values.Length) - { - break; - } - key += ":" + values[i]; - } - options.suppressWarnings.Add(key, key); - if (options.writeSuppressWarningsFile != null) - { - File.AppendAllText(options.writeSuppressWarningsFile.FullName, "-nowarn:" + key + Environment.NewLine); - } - string msg; - switch (msgId) - { - case Message.MainMethodFound: - msg = "Found main method in class \"{0}\""; - break; - case Message.OutputFileIs: - msg = "Output file is \"{0}\""; - break; - case Message.AutoAddRef: - msg = "Automatically adding reference to \"{0}\""; - break; - case Message.MainMethodFromManifest: - msg = "Using main class \"{0}\" based on jar manifest"; - break; - case Message.ClassNotFound: - msg = "Class \"{0}\" not found"; - break; - case Message.ClassFormatError: - msg = "Unable to compile class \"{0}\"" + Environment.NewLine + - " (class format error \"{1}\")"; - break; - case Message.DuplicateClassName: - msg = "Duplicate class name: \"{0}\""; - break; - case Message.IllegalAccessError: - msg = "Unable to compile class \"{0}\"" + Environment.NewLine + - " (illegal access error \"{1}\")"; - break; - case Message.VerificationError: - msg = "Unable to compile class \"{0}\"" + Environment.NewLine + - " (verification error \"{1}\")"; - break; - case Message.NoClassDefFoundError: - msg = "Unable to compile class \"{0}\"" + Environment.NewLine + - " (missing class \"{1}\")"; - break; - case Message.GenericUnableToCompileError: - msg = "Unable to compile class \"{0}\"" + Environment.NewLine + - " (\"{1}\": \"{2}\")"; - break; - case Message.DuplicateResourceName: - msg = "Skipping resource (name clash): \"{0}\""; - break; - case Message.SkippingReferencedClass: - msg = "Skipping class: \"{0}\"" + Environment.NewLine + - " (class is already available in referenced assembly \"{1}\")"; - break; - case Message.NoJniRuntime: - msg = "Unable to load runtime JNI assembly"; - break; - case Message.EmittedNoClassDefFoundError: - msg = "Emitted java.lang.NoClassDefFoundError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedIllegalAccessError: - msg = "Emitted java.lang.IllegalAccessError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedInstantiationError: - msg = "Emitted java.lang.InstantiationError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedIncompatibleClassChangeError: - msg = "Emitted java.lang.IncompatibleClassChangeError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedNoSuchFieldError: - msg = "Emitted java.lang.NoSuchFieldError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedAbstractMethodError: - msg = "Emitted java.lang.AbstractMethodError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedNoSuchMethodError: - msg = "Emitted java.lang.NoSuchMethodError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedLinkageError: - msg = "Emitted java.lang.LinkageError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedVerificationError: - msg = "Emitted java.lang.VerificationError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.EmittedClassFormatError: - msg = "Emitted java.lang.ClassFormatError in \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.InvalidCustomAttribute: - msg = "Error emitting \"{0}\" custom attribute" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.IgnoredCustomAttribute: - msg = "Custom attribute \"{0}\" was ignored" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.AssumeAssemblyVersionMatch: - msg = "Assuming assembly reference \"{0}\" matches \"{1}\", you may need to supply runtime policy"; - break; - case Message.InvalidDirectoryInLibOptionPath: - msg = "Directory \"{0}\" specified in -lib option is not valid"; - break; - case Message.InvalidDirectoryInLibEnvironmentPath: - msg = "Directory \"{0}\" specified in LIB environment is not valid"; - break; - case Message.LegacySearchRule: - msg = "Found assembly \"{0}\" using legacy search rule, please append '.dll' to the reference"; - break; - case Message.AssemblyLocationIgnored: - msg = "Assembly \"{0}\" is ignored as previously loaded assembly \"{1}\" has the same identity \"{2}\""; - break; - case Message.InterfaceMethodCantBeInternal: - msg = "Ignoring @ikvm.lang.Internal annotation on interface method" + Environment.NewLine + - " (\"{0}.{1}{2}\")"; - break; - case Message.NonPrimaryAssemblyReference: - msg = "Referenced assembly \"{0}\" is not the primary assembly of a shared class loader group, please reference primary assembly \"{1}\" instead"; - break; - case Message.MissingType: - msg = "Reference to type \"{0}\" claims it is defined in \"{1}\", but it could not be found"; - break; - case Message.MissingReference: - msg = "The type '{0}' is defined in an assembly that is not referenced. You must add a reference to assembly '{1}'"; - break; - case Message.DuplicateAssemblyReference: - msg = "Duplicate assembly reference \"{0}\""; - break; - case Message.UnableToResolveType: - msg = "Reference in \"{0}\" to type \"{1}\" claims it is defined in \"{2}\", but it could not be found"; - break; - case Message.StubsAreDeprecated: - msg = "Compiling stubs is deprecated. Please add a reference to assembly \"{0}\" instead."; - break; - case Message.WrongClassName: - msg = "Unable to compile \"{0}\" (wrong name: \"{1}\")"; - break; - case Message.ReflectionCallerClassRequiresCallerID: - msg = "Reflection.getCallerClass() called from non-CallerID method" + Environment.NewLine + - " (\"{0}.{1}{2}\")"; - break; - case Message.LegacyAssemblyAttributesFound: - msg = "Legacy assembly attributes container found. Please use the -assemblyattributes: option."; - break; - case Message.UnableToCreateLambdaFactory: - msg = "Unable to create static lambda factory."; - break; - case Message.UnableToCreateProxy: - msg = "Unable to create proxy \"{0}\"" + Environment.NewLine + - " (\"{1}\")"; - break; - case Message.DuplicateProxy: - msg = "Duplicate proxy \"{0}\""; - break; - case Message.MapXmlUnableToResolveOpCode: - msg = "Unable to resolve opcode in remap file: {0}"; - break; - case Message.MapXmlError: - msg = "Error in remap file: {0}"; - break; - case Message.InputFileNotFound: - msg = "Source file '{0}' not found"; - break; - case Message.UnknownFileType: - msg = "Unknown file type: {0}"; - break; - case Message.UnknownElementInMapFile: - msg = "Unknown element {0} in remap file, line {1}, column {2}"; - break; - case Message.UnknownAttributeInMapFile: - msg = "Unknown attribute {0} in remap file, line {1}, column {2}"; - break; - case Message.InvalidMemberNameInMapFile: - msg = "Invalid {0} name '{1}' in remap file in class {2}"; - break; - case Message.InvalidMemberSignatureInMapFile: - msg = "Invalid {0} signature '{3}' in remap file for {0} {1}.{2}"; - break; - case Message.InvalidPropertyNameInMapFile: - msg = "Invalid property {0} name '{3}' in remap file for property {1}.{2}"; - break; - case Message.InvalidPropertySignatureInMapFile: - msg = "Invalid property {0} signature '{3}' in remap file for property {1}.{2}"; - break; - case Message.UnknownWarning: - msg = "{0}"; - break; - case Message.CallerSensitiveOnUnsupportedMethod: - msg = "CallerSensitive annotation on unsupported method" + Environment.NewLine + - " (\"{0}.{1}{2}\")"; - break; - case Message.RemappedTypeMissingDefaultInterfaceMethod: - msg = "{0} does not implement default interface method {1}"; - break; - default: - throw new InvalidProgramException(); - } - bool error = msgId >= Message.StartErrors - || (options.warnaserror && msgId >= Message.StartWarnings) - || options.errorWarnings.ContainsKey(key) - || options.errorWarnings.ContainsKey(((int)msgId).ToString()); - Console.Error.Write("{0} IKVMC{1:D4}: ", error ? "error" : msgId < Message.StartWarnings ? "note" : "warning", (int)msgId); - if (error && Message.StartWarnings <= msgId && msgId < Message.StartErrors) - { - Console.Error.Write("Warning as Error: "); - } - Console.Error.WriteLine(msg, values); - if (options != this.rootTarget && options.path != null) - { - Console.Error.WriteLine(" (in {0})", options.path); - } - if (error) - { - if (++errorCount == 100) - { - throw new FatalCompilerErrorException(Message.MaximumErrorCountReached); - } - } + IkvmImporterInternal.IssueMessage(this, options, msgId, values); } } diff --git a/src/IKVM.Tools.Runner/Importer/IkvmImporterLauncher.cs b/src/IKVM.Tools.Runner/Importer/IkvmImporterLauncher.cs index 2a4673a91b..d695a68f5c 100644 --- a/src/IKVM.Tools.Runner/Importer/IkvmImporterLauncher.cs +++ b/src/IKVM.Tools.Runner/Importer/IkvmImporterLauncher.cs @@ -299,7 +299,7 @@ public async Task ExecuteAsync(IkvmImporterOptions options, CancellationTok await LogEvent(IkvmToolDiagnosticEventLevel.Debug, "Executing {0} {1}", cli.TargetFilePath, cli.Arguments); // send output to MSBuild - cli = cli.WithStandardErrorPipe(PipeTarget.ToDelegate(i => LogEvent(IkvmToolDiagnosticEventLevel.Error, i))); + cli = cli.WithStandardErrorPipe(PipeTarget.ToDelegate(i => LogEvent(IkvmToolDiagnosticEventLevel.Warning, i))); cli = cli.WithStandardOutputPipe(PipeTarget.ToDelegate(i => LogEvent(IkvmToolDiagnosticEventLevel.Debug, i))); // combine manual cancellation with timeout