1 using System; 2 using System.Collections.Generic; 3 using System.Diagnostics; 4 using System.Diagnostics.CodeAnalysis; 5 using System.IO; 6 using System.Linq; 7 using System.Runtime.CompilerServices; 8 using JetBrains.Annotations; 9 10 namespace GodotTools.Utils 11 { 12 [SuppressMessage("ReSharper", "InconsistentNaming")] 13 public static class OS 14 { 15 [MethodImpl(MethodImplOptions.InternalCall)] GetPlatformName()16 static extern string GetPlatformName(); 17 18 [MethodImpl(MethodImplOptions.InternalCall)] UnixFileHasExecutableAccess(string filePath)19 static extern bool UnixFileHasExecutableAccess(string filePath); 20 21 public static class Names 22 { 23 public const string Windows = "Windows"; 24 public const string OSX = "OSX"; 25 public const string X11 = "X11"; 26 public const string Server = "Server"; 27 public const string UWP = "UWP"; 28 public const string Haiku = "Haiku"; 29 public const string Android = "Android"; 30 public const string iOS = "iOS"; 31 public const string HTML5 = "HTML5"; 32 } 33 34 public static class Platforms 35 { 36 public const string Windows = "windows"; 37 public const string OSX = "osx"; 38 public const string X11 = "x11"; 39 public const string Server = "server"; 40 public const string UWP = "uwp"; 41 public const string Haiku = "haiku"; 42 public const string Android = "android"; 43 public const string iOS = "iphone"; 44 public const string HTML5 = "javascript"; 45 } 46 47 public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string> 48 { 49 [Names.Windows] = Platforms.Windows, 50 [Names.OSX] = Platforms.OSX, 51 [Names.X11] = Platforms.X11, 52 [Names.Server] = Platforms.Server, 53 [Names.UWP] = Platforms.UWP, 54 [Names.Haiku] = Platforms.Haiku, 55 [Names.Android] = Platforms.Android, 56 [Names.iOS] = Platforms.iOS, 57 [Names.HTML5] = Platforms.HTML5 58 }; 59 IsOS(string name)60 private static bool IsOS(string name) 61 { 62 return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); 63 } 64 IsAnyOS(IEnumerable<string> names)65 private static bool IsAnyOS(IEnumerable<string> names) 66 { 67 return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase)); 68 } 69 70 private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows)); 71 private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX)); 72 private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11)); 73 private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server)); 74 private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); 75 private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku)); 76 private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android)); 77 private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS)); 78 private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5)); 79 private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms)); 80 81 public static bool IsWindows => _isWindows.Value || IsUWP; 82 public static bool IsOSX => _isOSX.Value; 83 public static bool IsX11 => _isX11.Value; 84 public static bool IsServer => _isServer.Value; 85 public static bool IsUWP => _isUWP.Value; 86 public static bool IsHaiku => _isHaiku.Value; 87 public static bool IsAndroid => _isAndroid.Value; 88 public static bool IsiOS => _isiOS.Value; 89 public static bool IsHTML5 => _isHTML5.Value; 90 91 private static readonly string[] UnixLikePlatforms = {Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS}; 92 93 public static bool IsUnixLike => _isUnixLike.Value; 94 95 public static char PathSep => IsWindows ? ';' : ':'; 96 PathWhich([NotNull] string name)97 public static string PathWhich([NotNull] string name) 98 { 99 return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name); 100 } 101 PathWhichWindows([NotNull] string name)102 private static string PathWhichWindows([NotNull] string name) 103 { 104 string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? new string[] { }; 105 string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); 106 107 var searchDirs = new List<string>(); 108 109 if (pathDirs != null) 110 searchDirs.AddRange(pathDirs); 111 112 string nameExt = Path.GetExtension(name); 113 bool hasPathExt = !string.IsNullOrEmpty(nameExt) && windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); 114 115 searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list 116 117 if (hasPathExt) 118 return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists); 119 120 return (from dir in searchDirs 121 select Path.Combine(dir, name) 122 into path 123 from ext in windowsExts 124 select path + ext).FirstOrDefault(File.Exists); 125 } 126 PathWhichUnix([NotNull] string name)127 private static string PathWhichUnix([NotNull] string name) 128 { 129 string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); 130 131 var searchDirs = new List<string>(); 132 133 if (pathDirs != null) 134 searchDirs.AddRange(pathDirs); 135 136 searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list 137 138 return searchDirs.Select(dir => Path.Combine(dir, name)) 139 .FirstOrDefault(path => File.Exists(path) && UnixFileHasExecutableAccess(path)); 140 } 141 RunProcess(string command, IEnumerable<string> arguments)142 public static void RunProcess(string command, IEnumerable<string> arguments) 143 { 144 // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead 145 string CmdLineArgsToString(IEnumerable<string> args) 146 { 147 // Not perfect, but as long as we are careful... 148 return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); 149 } 150 151 var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) 152 { 153 RedirectStandardOutput = true, 154 RedirectStandardError = true, 155 UseShellExecute = false 156 }; 157 158 using (Process process = Process.Start(startInfo)) 159 { 160 if (process == null) 161 throw new Exception("No process was started"); 162 163 process.BeginOutputReadLine(); 164 process.BeginErrorReadLine(); 165 if (IsWindows && process.Id > 0) 166 User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself 167 } 168 } 169 ExecuteCommand(string command, IEnumerable<string> arguments)170 public static int ExecuteCommand(string command, IEnumerable<string> arguments) 171 { 172 // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead 173 string CmdLineArgsToString(IEnumerable<string> args) 174 { 175 // Not perfect, but as long as we are careful... 176 return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); 177 } 178 179 var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)); 180 181 Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}"); 182 183 // Print the output 184 startInfo.RedirectStandardOutput = false; 185 startInfo.RedirectStandardError = false; 186 187 startInfo.UseShellExecute = false; 188 189 using (var process = new Process {StartInfo = startInfo}) 190 { 191 process.Start(); 192 process.WaitForExit(); 193 194 return process.ExitCode; 195 } 196 } 197 } 198 } 199