diff --git a/src/AnalysisPrograms/CheckEnvironment.cs b/src/AnalysisPrograms/CheckEnvironment.cs index 59efa8b58..31fc1b89b 100644 --- a/src/AnalysisPrograms/CheckEnvironment.cs +++ b/src/AnalysisPrograms/CheckEnvironment.cs @@ -6,6 +6,7 @@ namespace AnalysisPrograms { using System; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -14,6 +15,7 @@ namespace AnalysisPrograms using Acoustics.Shared; using Acoustics.Tools.Audio; using AnalysisPrograms.AnalyseLongRecordings; + using AnalysisPrograms.Production; using AnalysisPrograms.Production.Arguments; using log4net; using McMaster.Extensions.CommandLineUtils; @@ -24,9 +26,9 @@ public class CheckEnvironment private static readonly ILog Log = LogManager.GetLogger(nameof(CheckEnvironment)); - private void Execute(Arguments arguments) + private int Execute(Arguments arguments) { - List errors = new List(); + var errors = new List(); Log.Info("Checking required executables can be found"); // master audio utility checks for available executables @@ -36,7 +38,12 @@ private void Execute(Arguments arguments) } catch (Exception ex) { - errors.Add(ex); + errors.Add(ex.Message); + } + + if (MainEntry.CheckForDataAnnotations() is string message) + { + errors.Add(message); } if (AppConfigHelper.IsMono) @@ -45,10 +52,10 @@ private void Execute(Arguments arguments) if (type != null) { MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); - if (displayName != null) + + if (displayName?.Invoke(null, null) is string name) { - var name = displayName.Invoke(null, null); - var version = Regex.Match(name as string, @".*(\d+\.\d+\.\d+\.\d+).*").Groups[1].Value; + var version = Regex.Match(name, @".*(\d+\.\d+\.\d+\.\d+).*").Groups[1].Value; Console.WriteLine(version); if (new Version(version) > new Version(5, 5)) { @@ -56,12 +63,12 @@ private void Execute(Arguments arguments) } else { - errors.Add(new Exception($"Mono version is {name}, we require at least Mono 5.5")); + errors.Add($"Mono version is {name}, we require at least Mono 5.5"); } } else { - errors.Add(new Exception("Could not check Mono version")); + errors.Add("Could not get Mono display name"); } } } @@ -70,10 +77,18 @@ private void Execute(Arguments arguments) if (errors.Count == 0) { Log.Success("Valid environment"); + + return ExceptionLookup.Ok; } else { - throw new AggregateException(errors.ToArray()); + foreach (var error in errors) + { + Log.Error(error); + } + + // not using exception lookup on purpose - it's static constructor loads more types + return ExceptionLookup.UnhandledExceptionErrorCode; } } @@ -85,8 +100,7 @@ public class Arguments : SubCommandBase public override Task Execute(CommandLineApplication app) { var instance = new CheckEnvironment(); - instance.Execute(this); - return this.Ok(); + return Task.FromResult(instance.Execute(this)); } } diff --git a/src/AnalysisPrograms/MainEntry.cs b/src/AnalysisPrograms/MainEntry.cs index 17eabc44a..7dbdeccde 100644 --- a/src/AnalysisPrograms/MainEntry.cs +++ b/src/AnalysisPrograms/MainEntry.cs @@ -36,7 +36,7 @@ public static async Task Main(string[] args) Copyright(); - AttachExceptionHandler(); + PrepareForErrors(); NoConsole.Log.Info($"Executable called with these arguments: {NewLine}{CommandLine}{NewLine}"); diff --git a/src/AnalysisPrograms/Production/Exceptions.cs b/src/AnalysisPrograms/Production/Exceptions.cs index e71c95e7a..3e4e60dc2 100644 --- a/src/AnalysisPrograms/Production/Exceptions.cs +++ b/src/AnalysisPrograms/Production/Exceptions.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // // All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). // @@ -19,102 +19,96 @@ namespace AnalysisPrograms.Production public static class ExceptionLookup { /// - /// The default exit code to use for exceptions not recognised. Must not be greater than 255. + /// The default exit code to use for exceptions not recognized. Must not be greater than 255. /// public const int UnhandledExceptionErrorCode = 200; - internal static readonly Dictionary ErrorLevels; - - static ExceptionLookup() - { - // WARNING: EXIT CODES CANNOT BE > 255 (for linux compatibility) - ErrorLevels = new Dictionary - { - { - typeof(ValidationException), - new ExceptionStyle { ErrorCode = ValiationError, PrintUsage = false } - }, - { - typeof(CommandLineArgumentException), - new ExceptionStyle() { ErrorCode = 3 } - }, - { - typeof(CommandParsingException), - new ExceptionStyle() { ErrorCode = 4 } - }, - { - typeof(DirectoryNotFoundException), - - // disabled print usage because these exceptions happen at all levels of the stack - new ExceptionStyle { ErrorCode = 51, PrintUsage = false } - }, - { - typeof(FileNotFoundException), - - // disabled print usage because these exceptions happen at all levels of the stack - new ExceptionStyle { ErrorCode = 52, PrintUsage = false } - }, - { - typeof(InvalidDurationException), - new ExceptionStyle { ErrorCode = 100 } - }, - { - typeof(InvalidStartOrEndException), - new ExceptionStyle { ErrorCode = 101 } - }, - { - typeof(InvalidFileDateException), - new ExceptionStyle() { ErrorCode = 102, PrintUsage = false } - }, - { - typeof(ConfigFileException), - new ExceptionStyle() { ErrorCode = 103, PrintUsage = false } - }, - { - typeof(AudioRecordingTooShortException), - new ExceptionStyle() { ErrorCode = 104, PrintUsage = false } - }, - { - typeof(InvalidAudioChannelException), - new ExceptionStyle() { ErrorCode = 105, PrintUsage = false } - }, - { - typeof(AnalysisOptionDevilException), - new ExceptionStyle - { - ErrorCode = 66, - Handle = false, - } - }, - { - typeof(NoDeveloperMethodException), - new ExceptionStyle { ErrorCode = 199 } - }, - { - typeof(Exception), - new ExceptionStyle - { - ErrorCode = - UnhandledExceptionErrorCode, - } - }, - }; - } + private static Dictionary levels; public static int Ok => 0; - public static int ValiationError => 2; + public static int ValidationError => 2; public static int ActionRequired => 2; public static int NoData => 10; - public static int SpecialExceptionErrorLevel + internal static Dictionary ErrorLevels => levels ?? (levels = CreateExceptionMap()); + + private static Dictionary CreateExceptionMap() { - get + // WARNING: EXIT CODES CANNOT BE > 255 (for linux compatibility) + return new Dictionary { - return ErrorLevels[typeof(Exception)].ErrorCode; - } + { + typeof(ValidationException), + new ExceptionStyle { ErrorCode = ValidationError, PrintUsage = false } + }, + { + typeof(CommandLineArgumentException), + new ExceptionStyle() { ErrorCode = 3 } + }, + { + typeof(CommandParsingException), + new ExceptionStyle() { ErrorCode = 4 } + }, + { + typeof(DirectoryNotFoundException), + + // disabled print usage because these exceptions happen at all levels of the stack + new ExceptionStyle { ErrorCode = 51, PrintUsage = false } + }, + { + typeof(FileNotFoundException), + + // disabled print usage because these exceptions happen at all levels of the stack + new ExceptionStyle { ErrorCode = 52, PrintUsage = false } + }, + { + typeof(InvalidDurationException), + new ExceptionStyle { ErrorCode = 100 } + }, + { + typeof(InvalidStartOrEndException), + new ExceptionStyle { ErrorCode = 101 } + }, + { + typeof(InvalidFileDateException), + new ExceptionStyle() { ErrorCode = 102, PrintUsage = false } + }, + { + typeof(ConfigFileException), + new ExceptionStyle() { ErrorCode = 103, PrintUsage = false } + }, + { + typeof(AudioRecordingTooShortException), + new ExceptionStyle() { ErrorCode = 104, PrintUsage = false } + }, + { + typeof(InvalidAudioChannelException), + new ExceptionStyle() { ErrorCode = 105, PrintUsage = false } + }, + { + typeof(AnalysisOptionDevilException), + new ExceptionStyle + { + ErrorCode = 66, + Handle = false, + } + }, + { + typeof(NoDeveloperMethodException), + new ExceptionStyle { ErrorCode = 199 } + }, + { + typeof(Exception), + new ExceptionStyle + { + ErrorCode = + UnhandledExceptionErrorCode, + } + }, + }; } public class ExceptionStyle diff --git a/src/AnalysisPrograms/Production/MainEntryUtilities.cs b/src/AnalysisPrograms/Production/MainEntryUtilities.cs index 368c8ce56..8f74fe764 100644 --- a/src/AnalysisPrograms/Production/MainEntryUtilities.cs +++ b/src/AnalysisPrograms/Production/MainEntryUtilities.cs @@ -199,6 +199,36 @@ internal static void BeforeExecute(MainArgs main, CommandLineApplication applica LoadNativeCode(); } + /// + /// Checks to see whether we can load a DLL that we depend on from the GAC. + /// We have to check this early on or else we get some pretty hard to + /// diagnose errors (and also because our CLI parser depends on it). + /// + /// A message if there is a problem. + internal static string CheckForDataAnnotations() + { + // https://github.com/QutEcoacoustics/audio-analysis/issues/225 + try + { + Assembly.Load( + "System.ComponentModel.DataAnnotations, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); + return null; + } + catch (FileNotFoundException fnfex) + { + return $@"{fnfex.ToString()} +! +! +! +In cases where System.ComponentModel.DataAnnotations is not found we have +previously found that the mono install is corrupt. Try installing mono again +and make sure you install the `mono-complete` package. +! +! +!"; + } + } + internal static void Copyright() { LoggedConsole.WriteLine( @@ -338,9 +368,19 @@ internal static CommandLineApplication CreateCommandLineApplication() return app; } - private static void AttachExceptionHandler() + /// + /// Attaches an exception handler and also does a check + /// to see if necessary DLLs exist. WARNING: can quit process!. + /// + private static void PrepareForErrors() { - Environment.ExitCode = ExceptionLookup.SpecialExceptionErrorLevel; + ExitCode = ExceptionLookup.UnhandledExceptionErrorCode; + + if (CheckForDataAnnotations() is string message) + { + Console.WriteLine(message); + Exit(ExceptionLookup.UnhandledExceptionErrorCode); + } AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException; } @@ -403,7 +443,7 @@ private static void CurrentDomainOnUnhandledException(object sender, UnhandledEx PrintAggregateException(ex); } - int returnCode = style?.ErrorCode ?? ExceptionLookup.SpecialExceptionErrorLevel; + int returnCode = style?.ErrorCode ?? ExceptionLookup.UnhandledExceptionErrorCode; // finally return error level NoConsole.Log.Info("ERRORLEVEL: " + returnCode);