using System; using System.IO; using System.Linq; using System.Reflection; using OpenTap; namespace Keysight.OpenTap.Sdk.MSBuild { /// /// Build actions which require OpenTAP should enter this context before referencing any OpenTAP types. /// This is necessary because the OpenTAP dll is not in the same directory as this dll. /// In addition, this context initializes session logs with a specific name, and with /// a flag that allows them to be deleted so e.g. bin/Debug can be deleted without errors. /// class OpenTapContext : IDisposable { // The static portion of this type MUST NOT reference OpenTAP. #region Static Members private static object loadLock = new object(); private static void loadOpenTap(string tapDir, string runtimeDir) { // This alters the value returned by 'ExecutorClient.ExeDir' which would otherwise return the location of // OpenTap.dll which in an MSBuild context would be the nuget directory which leads to unexpected behavior // because the expected location is the build directory in all common use cases. Environment.SetEnvironmentVariable("OPENTAP_INIT_DIRECTORY", tapDir, EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("OPENTAP_NO_UPDATE_CHECK", "true"); Environment.SetEnvironmentVariable("OPENTAP_DEBUG_INSTALL", "true"); lock (loadLock) { var loaded = AppDomain.CurrentDomain.GetAssemblies().Select(l => l.GetName().Name).ToArray(); string[] assemblies = { "OpenTap", "OpenTap.Package" }; foreach (var asmName in assemblies) { if (loaded.Contains(asmName)) continue; // already loaded - continue Assembly.LoadFrom(Path.Combine(runtimeDir, $"{asmName}.dll")); } } } /// /// Help the runtime resolve OpenTAP. The correct OpenTAP dll is in a subdirectory and will not be found by /// the default resolver. Also, the resolver will look for the debug version (9.4.0.0) because that's what this /// assembly was compiled against. This is sort of a hack, but it should be fine. /// /// public static IDisposable Create(string tapDir, string runtimeDir) { loadOpenTap(tapDir, runtimeDir); return new OpenTapContext(tapDir); } /// /// Increment a number until we find an available filename. /// /// /// static string numberedFileName(string logName) { var logNameNoExt = Path.Combine(Path.GetDirectoryName(logName), Path.GetFileNameWithoutExtension(logName)); var num = 1; while (File.Exists(logName)) { logName = $"{logNameNoExt} ({num}).txt"; num += 1; } return logName; } #endregion #region Instance Members /// /// Calling this method forces the runtime to resolve OpenTAP because SessionLogs is from the OpenTAP assembly. /// public OpenTapContext(string tapDir) { var buildProc = System.Diagnostics.Process.GetCurrentProcess(); var timestamp = buildProc.StartTime.ToString("yyyy-MM-dd HH-mm-ss"); var pid = buildProc.Id; string pathEnding = $"SessionLog.{pid} {timestamp}"; if (Assembly.GetEntryAssembly() != null && !String.IsNullOrWhiteSpace(Assembly.GetEntryAssembly().Location)) { string exeName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location); // Path example: /SessionLogs/tap/tap .txt pathEnding = $"{exeName}.{pid} {timestamp}"; } var logName = Path.GetFullPath(Path.Combine(tapDir, "SessionLogs", pathEnding + ".txt")); logName = numberedFileName(logName); SessionLogs.Initialize(logName, true); } /// /// Ensure the session log is flushed when this context is disposed. /// public void Dispose() { Log.Flush(); SessionLogs.Flush(); } #endregion } }