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