chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using OpenTap;
 
namespace Keysight.OpenTap.Sdk.MSBuild
{
    /// <summary>
    /// 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. 
    /// </summary>
    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"));
                }
            }
        }
 
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="runtimeDir"></param>
        public static IDisposable Create(string tapDir, string runtimeDir)
        {
            loadOpenTap(tapDir, runtimeDir);
            return new OpenTapContext(tapDir);
        }
        
        /// <summary>
        /// Increment a number until we find an available filename.
        /// </summary>
        /// <param name="logName"></param>
        /// <returns></returns>
        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
        
        /// <summary>
        /// Calling this method forces the runtime to resolve OpenTAP because SessionLogs is from the OpenTAP assembly.
        /// </summary>
        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: <TapDir>/SessionLogs/tap/tap <timestamp>.txt
                pathEnding = $"{exeName}.{pid} {timestamp}";
            }
 
            var logName = Path.GetFullPath(Path.Combine(tapDir, "SessionLogs", pathEnding + ".txt"));
            logName = numberedFileName(logName);
 
            SessionLogs.Initialize(logName, true);
        }
 
        /// <summary>
        /// Ensure the session log is flushed when this context is disposed.
        /// </summary>
        public void Dispose()
        {
            Log.Flush();
            SessionLogs.Flush();
        }
        #endregion
    }
}