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&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.");
}
}
}