csharp/NativeUtils/VariablesMapper.cs (348 lines of code) (raw):

//See https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives for preprocessor directives #if NETCOREAPP2_0_OR_GREATER || NET6_0 || NET5_0 || NETCOREAPP3_1 || NETCOREAPP3_0 || NETCOREAPP2_2 || NETCOREAPP2_1 || NETCOREAPP2_0 #define NETCOREAPP2_0_OR_GREATER #endif #if NETCOREAPP1_0_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NETCOREAPP1_1 || NETCOREAPP1_0 || NETCOREAPP #define NETCOREAPP1_0_OR_GREATER #endif #if NET471_OR_GREATER || NET48 || NET472 || NET471 #define NET471_OR_GREATER #endif #if NET45_OR_GREATER || NET471_OR_GREATER || NET47 || NET462 || NET461 || NET46 || NET452 || NET451 || NET45 #define NET45_OR_GREATER #endif #if NET40_OR_GREATER || NET45_OR_GREATER || NET40 #define NET40_OR_GREATER #endif #if NETSTANDARD2_0_OR_GREATER || NETSTANDARD2_1 || NETSTANDARD2_0 #define NETSTANDARD2_0_OR_GREATER #endif #if NETSTANDARD1_1_OR_GREATER || NETSTANDARD2_0_OR_GREATER || NETSTANDARD1_6 || NETSTANDARD1_5 || NETSTANDARD1_4 || NETSTANDARD1_3 || NETSTANDARD1_2 || NETSTANDARD1_1 #define NETSTANDARD1_1_OR_GREATER #endif #if NETSTANDARD1_0_OR_GREATER || NETSTANDARD1_1_OR_GREATER || NETSTANDARD1_0 || NETSTANDARD #define NETSTANDARD1_0_OR_GREATER #endif #if NETCOREAPP1_0_OR_GREATER || NET40_OR_GREATER || NETSTANDARD2_0_OR_GREATER #define STACK_TRACE_DETECT_CALLER #endif #if NETCOREAPP1_0_OR_GREATER || NET471_OR_GREATER || NETSTANDARD1_1_OR_GREATER #define DETECT_OS_AND_ARCH_FROM_RUNTIMEINFO #endif #if NETSTANDARD1_0_OR_GREATER || NET45_OR_GREATER || NETCOREAPP2_0_OR_GREATER #define GET_ASSEMBLY_VIA_TYPEINFO #endif using System; using System.IO; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Security.Cryptography; using System.Text.RegularExpressions; using System.Reflection; using System.Linq; using System.Diagnostics; namespace EPAM.Deltix.Utilities { class VariablesMapper { public static readonly string OsDetected; public static readonly string ArchDetected; public static readonly bool IsOsWindowsDetected; public static readonly bool IsOsLinuxDetected; public static readonly bool IsOsDarwinDetected; static VariablesMapper() { OsDetected = GetOsName(); IsOsWindowsDetected = OsDetected == OS.windows.ToString(); IsOsLinuxDetected = OsDetected == OS.linux.ToString(); IsOsDarwinDetected = OsDetected == OS.darwin.ToString(); ArchDetected = GetArchName(); } public enum Variables { TEMP, RANDOM, TIME, OS, ARCH, VERSION, PACKAGE, PACKAGE_LAST, } internal enum OS { windows, linux, darwin } internal enum ARCH { i386, amd64, arm, aarch64 } protected Dictionary<string, string> variables = new Dictionary<string, string>(); internal static Assembly GetAssembly(Type clazz) { #if GET_ASSEMBLY_VIA_TYPEINFO return clazz.GetTypeInfo().Assembly; #else return clazz.Assembly; #endif } internal static string GetVersion(Assembly assembly) { #if NETSTANDARD2_0_OR_GREATER return assembly.CustomAttributes .Where(x => x.AttributeType == typeof(AssemblyFileVersionAttribute)) .First().ConstructorArguments.First().Value.ToString(); #else return assembly.GetName().Version.ToString(); #endif } #if STACK_TRACE_DETECT_CALLER internal static Type[] knownTypes = { typeof(VariablesMapper), typeof(ResourceLoader), typeof(FileJanitor), typeof(IResourceLoaderBase), typeof(IResourceLoaderDone), typeof(IResourceLoaderInstance), typeof(IResourceLoaderInstanceFrom), typeof(IResourceLoaderInstanceTo), typeof(ResourceLoaderUtils.Diagnostics), typeof(ResourceLoaderUtils.FileStream), typeof(ResourceLoaderUtils.FileStreamImpl), typeof(ResourceLoaderUtils.Logger), typeof(ResourceLoaderUtils.Util) }; #endif public static Type TryDetectCaller() { #if STACK_TRACE_DETECT_CALLER StackTrace st = new StackTrace(); for (int i = 0; i < st.FrameCount; ++i) { StackFrame sf = st.GetFrame(i); if (sf == null) return null; // It is dangeroud to skip unkown frames, so just report I don't know. MethodBase m = sf.GetMethod(); if (m == null) return null; Type dt = m.DeclaringType; if (dt == null) return null; if (!knownTypes.Any(knownType => dt.IsAssignableFrom(knownType))) return dt; } return null; #else return null; #endif } public VariablesMapper(Type clazz) : this(clazz, GetVersion(GetAssembly(clazz))) { } public VariablesMapper(Type clazz, string version) { variables[Variables.VERSION.ToString()] = version; string tmpDir = Path.GetTempPath(); while (tmpDir.EndsWith("\\") || tmpDir.EndsWith("/")) tmpDir = tmpDir.Substring(0, tmpDir.Length - 1); variables[Variables.TEMP.ToString()] = tmpDir; variables[Variables.OS.ToString()] = OsDetected; variables[Variables.ARCH.ToString()] = ArchDetected; var packageName = clazz.Namespace; variables[Variables.PACKAGE.ToString()] = packageName; variables[Variables.PACKAGE_LAST.ToString()] = packageName.Substring(packageName.LastIndexOf('.') + 1); } internal static string GetOsName() { #if DETECT_OS_AND_ARCH_FROM_RUNTIMEINFO if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OS.windows.ToString(); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return OS.darwin.ToString(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return OS.linux.ToString(); throw new Exception("Unknow operation system."); #else // .NET std/core >= 2.0 supports both methods int p = (int)Environment.OSVersion.Platform; if (p < 4) return OS.windows.ToString(); if (p == 6) /* Old Mac OS value */ return OS.darwin.ToString(); if (p == 4 || p == 128) /* Any Unix */ return ReadProcessOutput("uname").Contains("Darwin") ? OS.darwin.ToString() : OS.linux.ToString(); throw new Exception("Unknow operation system."); #endif } // Needed only for .NET Framework (Actually called on Mono + Unix OS-es) #if !DETECT_OS_AND_ARCH_FROM_RUNTIMEINFO private static string ReadProcessOutput(string name, string args = "") { try { var startInfo = new ProcessStartInfo(name, args); startInfo.UseShellExecute = false; startInfo.RedirectStandardOutput = true; var p = Process.Start(startInfo); string output = p.StandardOutput.ReadToEnd(); p.WaitForExit(); return output == null ? "" : output.Trim(); } catch { return ""; } } #endif /// <summary> /// See the /// https://apisof.net/catalog/System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform) /// https://docs.microsoft.com/ru-ru/dotnet/api/system.environment.osversion?redirectedfrom=MSDN&amp;view=netframework-4.7.2#System_Environment_OSVersion /// http://mono.wikia.com/wiki/Detecting_the_execution_platform /// </summary> /// <returns>Internal enum</returns> internal static string GetArchName() { #if DETECT_OS_AND_ARCH_FROM_RUNTIMEINFO switch (RuntimeInformation.ProcessArchitecture) { case Architecture.X86: return ARCH.i386.ToString(); case Architecture.X64: return ARCH.amd64.ToString(); case Architecture.Arm: return ARCH.arm.ToString(); case Architecture.Arm64: return ARCH.aarch64.ToString(); default: throw new Exception("Can't detect architecture."); //return RuntimeInformation.ProcessArchitecture.ToString(); } #else if (IsOsWindowsDetected) { return 8 == IntPtr.Size ? ARCH.amd64.ToString() : ARCH.i386.ToString(); } else if (IsOsLinuxDetected) { string arch = LinuxArch(); if (arch != null) return arch; } else if (IsOsDarwinDetected) { return ARCH.amd64.ToString(); } throw new Exception("Can't detect architecture."); #endif } #if !DETECT_OS_AND_ARCH_FROM_RUNTIMEINFO static string LinuxArch() { // https://stackoverflow.com/questions/2565282/difference-between-machine-hardware-and-hardware-platform string arch = LinuxArchNameTryParse(ReadProcessOutput("uname", "--hardware-platform")); // the 'unknown' string can be returned if (arch != null) return arch; arch = LinuxArchNameTryParse(ReadProcessOutput("uname", "--processor")); if (arch != null) return arch; return LinuxArchNameTryParse(ReadProcessOutput("uname", "--machine")); } static string LinuxArchNameTryParse(string arch) { if (arch.Contains("aarch64")) return ARCH.aarch64.ToString(); if (arch.Contains("ARM")) return ARCH.arm.ToString(); string[] amd64Alias = new string[] { "x86-64", "x86_64", "AMD64", "Intel 64", "Intel64", "EM64T", "x64" }; if (amd64Alias.Any(a => a.Equals(arch))) return ARCH.amd64.ToString(); string[] x86Suffix = new string[] { "386", "586", "686" }; if (x86Suffix.Any(s => arch.Contains(s))) return ARCH.i386.ToString(); return null; } #endif public string Version => Get(Variables.VERSION.ToString()); public string Temp => Get(Variables.TEMP.ToString()); public string Os => Get(Variables.OS.ToString()); public string Arch => Get(Variables.ARCH.ToString()); public string Package => Get(Variables.PACKAGE.ToString()); public string PackageLast => Get(Variables.PACKAGE_LAST.ToString()); public static string Random { get { var cryptoResult = new byte[4]; RandomNumberGenerator.Create().GetBytes(cryptoResult); return (BitConverter.ToInt32(cryptoResult, 0) & int.MaxValue).ToString(); } } static long unixEpochOffset = new DateTime(1970, 1, 1).Ticks; public static string Time => ((DateTimeOffset.UtcNow.Ticks - unixEpochOffset) / TimeSpan.TicksPerMillisecond).ToString(); public string Get(string varName) { string varValue; if (variables.TryGetValue(varName, out varValue)) return varValue; if (varName.Equals(Variables.RANDOM.ToString())) return Random; if (varName.Equals(Variables.TIME.ToString())) return Time; return Environment.GetEnvironmentVariable(varName); } public VariablesMapper With(string varName, string varValue) { variables[varName] = varValue; return this; } public VariablesMapper WithVersion(string value) { return With(Variables.VERSION.ToString(), value); } public VariablesMapper WithTemp(string value) { return With(Variables.TEMP.ToString(), value); } public VariablesMapper WithOs(String value) { return With(Variables.OS.ToString(), value); } public VariablesMapper WithArch(String value) { return With(Variables.ARCH.ToString(), value); } public VariablesMapper WithPackage(String value) { return With(Variables.PACKAGE.ToString(), value); } public VariablesMapper WithPackageLast(String value) { return With(Variables.PACKAGE_LAST.ToString(), value); } public VariablesMapper WithRandom(String value) { return With(Variables.RANDOM.ToString(), value); } public VariablesMapper WithTime(String value) { return With(Variables.TIME.ToString(), value); } static Regex varPattern = new Regex(@"\$\((\w+?)\)"); public string Substitute(string path) { return Substitute(path, 256, 64 * 1024, false); } public string SubstituteAll(string path) { return Substitute(path, 256, 64 * 1024, true); } public string Substitute(String path, int maxIter, int maxLength, bool throwOnUnresolved) { for (int si = 0; si < maxIter; ++si) { var matchers = varPattern.Matches(path); bool isAnySubstituted = false; foreach (Match match in matchers) { var varName = match.Groups[1].Value; var varValue = Get(varName); if (varValue == null) { if (throwOnUnresolved) throw new Exception($"Can't resolve '{varName}' variable."); continue; } long nextPathLength = (long)path.Length - match.Length + varValue.Length; if (nextPathLength > maxLength) throw new Exception("The resulting path is too long."); path = path.Substring(0, match.Index) + varValue + path.Substring(match.Index + match.Length); isAnySubstituted = true; break; } if (!isAnySubstituted) return path; } throw new Exception("Too many substitutions. Cyclic substitutions are possible."); } } }