Skip to content

Commit f1a1ae5

Browse files
committed
environment: manually scan $PATH on POSIX systems
Because the `which` command is not always located in `/usr/bin` on all POSIX systems, we cannot directly call `/usr/bin/which` to do program location for us. Oh the irony.. We might be able to use `env` to locate `which`, but again the `env` utility isn't always found in the same place, so we're stuck. Instead we now manually scan the $PATH variable directories looking for the program we need to find, in the same way we do on Windows (for different reasons). This should also be slightly faster as you'd hope .NET's String.Split method and lstat calls are faster than fork/exec-ing another executable?
1 parent 4f02496 commit f1a1ae5

File tree

3 files changed

+33
-60
lines changed

3 files changed

+33
-60
lines changed

src/shared/Core/EnvironmentBase.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.IO;
45
using System.Linq;
56

67
namespace GitCredentialManager
@@ -89,8 +90,6 @@ public bool IsDirectoryOnPath(string directoryPath)
8990

9091
protected abstract string[] SplitPathVariable(string value);
9192

92-
public abstract bool TryLocateExecutable(string program, out string path);
93-
9493
public virtual Process CreateProcess(string path, string args, bool useShellExecute, string workingDirectory)
9594
{
9695
var psi = new ProcessStartInfo(path, args)
@@ -104,6 +103,38 @@ public virtual Process CreateProcess(string path, string args, bool useShellExec
104103

105104
return new Process { StartInfo = psi };
106105
}
106+
107+
public bool TryLocateExecutable(string program, out string path)
108+
{
109+
// On UNIX-like systems we would normally use the "which" utility to locate a program,
110+
// but since distributions don't always place "which" in a consistent location we cannot
111+
// find it! Oh the irony..
112+
// We could also try using "env" to then locate "which", but the same problem exists in
113+
// that "env" isn't always in a standard location.
114+
//
115+
// On Windows we should avoid using the equivalent utility "where.exe" because this will
116+
// include the current working directory in the search, and we don't want this.
117+
//
118+
// The upshot of the above means we cannot use either of "which" or "where.exe" and must
119+
// instead manually scan the PATH variable looking for the program.
120+
// At least both Windows and UNIX use the same name for the $PATH or %PATH% variable!
121+
if (Variables.TryGetValue("PATH", out string pathValue))
122+
{
123+
string[] paths = SplitPathVariable(pathValue);
124+
foreach (var basePath in paths)
125+
{
126+
string candidatePath = Path.Combine(basePath, program);
127+
if (FileSystem.FileExists(candidatePath))
128+
{
129+
path = candidatePath;
130+
return true;
131+
}
132+
}
133+
}
134+
135+
path = null;
136+
return false;
137+
}
107138
}
108139

109140
public static class EnvironmentExtensions

src/shared/Core/Interop/Posix/PosixEnvironment.cs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics;
4-
using System.Linq;
53

64
namespace GitCredentialManager.Interop.Posix
75
{
@@ -29,40 +27,6 @@ protected override string[] SplitPathVariable(string value)
2927
return value.Split(':');
3028
}
3129

32-
public override bool TryLocateExecutable(string program, out string path)
33-
{
34-
// The "which" utility scans over the PATH and does not include the current working directory
35-
// (unlike the equivalent "where.exe" on Windows), which is exactly what we want. Let's use it.
36-
const string whichPath = "/usr/bin/which";
37-
var psi = new ProcessStartInfo(whichPath, program)
38-
{
39-
UseShellExecute = false,
40-
RedirectStandardOutput = true
41-
};
42-
43-
using (var where = new Process {StartInfo = psi})
44-
{
45-
where.Start();
46-
where.WaitForExit();
47-
48-
switch (where.ExitCode)
49-
{
50-
case 0: // found
51-
string stdout = where.StandardOutput.ReadToEnd();
52-
string[] results = stdout.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
53-
path = results.First();
54-
return true;
55-
56-
case 1: // not found
57-
path = null;
58-
return false;
59-
60-
default:
61-
throw new Exception($"Unknown error locating '{program}' using {whichPath}. Exit code: {where.ExitCode}.");
62-
}
63-
}
64-
}
65-
6630
#endregion
6731

6832
private static IReadOnlyDictionary<string, string> GetCurrentVariables()

src/shared/Core/Interop/Windows/WindowsEnvironment.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -67,28 +67,6 @@ public override void RemoveDirectoryFromPath(string directoryPath, EnvironmentVa
6767
}
6868
}
6969

70-
public override bool TryLocateExecutable(string program, out string path)
71-
{
72-
// Don't use "where.exe" on Windows as this includes the current working directory
73-
// and we don't want to enumerate this location; only the PATH.
74-
if (Variables.TryGetValue("PATH", out string pathValue))
75-
{
76-
string[] paths = SplitPathVariable(pathValue);
77-
foreach (var basePath in paths)
78-
{
79-
string candidatePath = Path.Combine(basePath, program);
80-
if (FileSystem.FileExists(candidatePath))
81-
{
82-
path = candidatePath;
83-
return true;
84-
}
85-
}
86-
}
87-
88-
path = null;
89-
return false;
90-
}
91-
9270
public override Process CreateProcess(string path, string args, bool useShellExecute, string workingDirectory)
9371
{
9472
// If we're asked to start a WSL executable we must launch via the wsl.exe command tool

0 commit comments

Comments
 (0)