@@ -2,7 +2,16 @@ package shell
2
2
3
3
import (
4
4
"fmt"
5
+ "os"
6
+ "strconv"
5
7
"strings"
8
+
9
+ crcos "github.com/crc-org/crc/v2/pkg/os"
10
+ )
11
+
12
+ var (
13
+ CommandRunner = crcos .NewLocalCommandRunner ()
14
+ WindowsSubsystemLinuxKernelMetadataFile = "/proc/version"
6
15
)
7
16
8
17
type Config struct {
@@ -65,9 +74,9 @@ func GetEnvString(userShell string, envName string, envValue string) string {
65
74
case "cmd" :
66
75
return fmt .Sprintf ("SET %s=%s" , envName , envValue )
67
76
case "fish" :
68
- return fmt .Sprintf ("contains %s $fish_user_paths; or set -U fish_user_paths %s $fish_user_paths" , envValue , envValue )
77
+ return fmt .Sprintf ("contains %s $fish_user_paths; or set -U fish_user_paths %s $fish_user_paths" , convertToLinuxStylePath ( userShell , envValue ), convertToLinuxStylePath ( userShell , envValue ) )
69
78
default :
70
- return fmt .Sprintf ("export %s=\" %s\" " , envName , envValue )
79
+ return fmt .Sprintf ("export %s=\" %s\" " , envName , convertToLinuxStylePath ( userShell , envValue ) )
71
80
}
72
81
}
73
82
@@ -81,8 +90,140 @@ func GetPathEnvString(userShell string, prependedPath string) string {
81
90
case "cmd" :
82
91
pathStr = fmt .Sprintf ("%s;%%PATH%%" , prependedPath )
83
92
default :
84
- pathStr = fmt .Sprintf ("%s:$PATH" , prependedPath )
93
+ pathStr = fmt .Sprintf ("%s:$PATH" , convertToLinuxStylePath ( userShell , prependedPath ) )
85
94
}
86
95
87
96
return GetEnvString (userShell , "PATH" , pathStr )
88
97
}
98
+
99
+ // convertToLinuxStylePath is a utility method to translate Windows paths to Linux environments (e.g. Git Bash).
100
+ //
101
+ // It receives two arguments:
102
+ // - userShell : currently active shell
103
+ // - path : Windows path to be converted
104
+ //
105
+ // It returns Linux equivalent of the Windows path.
106
+ //
107
+ // For example, a Windows path like `C:\Users\foo\.crc\bin\oc` is converted into `/C/Users/foo/.crc/bin/oc`.
108
+ func convertToLinuxStylePath (userShell string , path string ) string {
109
+ if IsWindowsSubsystemLinux () {
110
+ return convertToWindowsSubsystemLinuxPath (path )
111
+ }
112
+ if strings .Contains (path , "\\ " ) &&
113
+ (userShell == "bash" || userShell == "zsh" || userShell == "fish" ) {
114
+ path = strings .ReplaceAll (path , ":" , "" )
115
+ path = strings .ReplaceAll (path , "\\ " , "/" )
116
+
117
+ return fmt .Sprintf ("/%s" , path )
118
+ }
119
+ return path
120
+ }
121
+
122
+ // convertToWindowsSubsystemLinuxPath is a utility method to translate between Windows and WSL(Windows Subsystem for
123
+ // Linux) paths. It relies on `wslpath` command to perform this conversion.
124
+ //
125
+ // It receives one argument:
126
+ // - path : Windows path to be converted to WSL path
127
+ //
128
+ // It returns translated WSL equivalent of provided windows path.
129
+ func convertToWindowsSubsystemLinuxPath (path string ) string {
130
+ stdOut , _ , err := CommandRunner .Run ("wsl" , "-e" , "bash" , "-c" , fmt .Sprintf ("wslpath -a '%s'" , path ))
131
+ if err != nil {
132
+ return path
133
+ }
134
+ return strings .TrimSpace (stdOut )
135
+ }
136
+
137
+ // IsWindowsSubsystemLinux detects whether current system is using Windows Subsystem for Linux or not
138
+ //
139
+ // It checks for these conditions to make sure that current system has WSL installed:
140
+ // - `/proc/version` file is present
141
+ // - `/proc/version` file contents contain keywords `Microsoft` and `WSL`
142
+ //
143
+ // It above conditions are met, then this method returns `true` otherwise `false`.
144
+ func IsWindowsSubsystemLinux () bool {
145
+ procVersionContent , err := os .ReadFile (WindowsSubsystemLinuxKernelMetadataFile )
146
+ if err != nil {
147
+ return false
148
+ }
149
+ if strings .Contains (string (procVersionContent ), "Microsoft" ) ||
150
+ strings .Contains (string (procVersionContent ), "WSL" ) {
151
+ return true
152
+ }
153
+ return false
154
+ }
155
+
156
+ // detectShellByInvokingCommand is a utility method that tries to detect current shell in use by invoking `ps` command.
157
+ // This method is extracted so that it could be used by unix systems as well as Windows (in case of WSL). It executes
158
+ // the command provided in the method arguments and then passes the output to inspectProcessOutputForRecentlyUsedShell
159
+ // for evaluation.
160
+ //
161
+ // It receives two arguments:
162
+ // - defaultShell : default shell to revert back to in case it's unable to detect.
163
+ // - command: command to be executed
164
+ // - args: a string array containing command arguments
165
+ //
166
+ // It returns a string value representing current shell.
167
+ func detectShellByInvokingCommand (defaultShell string , command string , args []string ) string {
168
+ stdOut , _ , err := CommandRunner .Run (command , args ... )
169
+ if err != nil {
170
+ return defaultShell
171
+ }
172
+
173
+ detectedShell := inspectProcessOutputForRecentlyUsedShell (stdOut )
174
+ if detectedShell == "" {
175
+ return defaultShell
176
+ }
177
+ return detectedShell
178
+ }
179
+
180
+ // inspectProcessOutputForRecentlyUsedShell inspects output of ps command to detect currently active shell session.
181
+ //
182
+ // Note : This method assumes that ps command has already sorted the processes by `pid` in reverse order.
183
+ // It parses the output into a struct, filters process types by name and returns the first element.
184
+ //
185
+ // It takes one argument:
186
+ //
187
+ // - psCommandOutput: output of ps command executed on a particular shell session
188
+ //
189
+ // It returns:
190
+ //
191
+ // - a string value (one of `zsh`, `bash` or `fish`) for current shell environment in use. If it's not able to determine
192
+ // underlying shell type, it returns and empty string.
193
+ //
194
+ // This method tries to check all processes open and filters out shell sessions (one of `zsh`, `bash` or `fish)
195
+ // It then returns first shell process.
196
+ //
197
+ // For example, if ps command gives this output:
198
+ //
199
+ // 2908 ps
200
+ // 2889 fish
201
+ // 823 bash
202
+ //
203
+ // Then this method would return `fish` as it's the first shell process.
204
+ func inspectProcessOutputForRecentlyUsedShell (psCommandOutput string ) string {
205
+ type ProcessOutput struct {
206
+ processID int
207
+ output string
208
+ }
209
+ var processOutputs []ProcessOutput
210
+ lines := strings .Split (psCommandOutput , "\n " )
211
+ for _ , line := range lines {
212
+ lineParts := strings .Split (strings .TrimSpace (line ), " " )
213
+ if len (lineParts ) == 2 && (strings .Contains (lineParts [1 ], "zsh" ) ||
214
+ strings .Contains (lineParts [1 ], "bash" ) ||
215
+ strings .Contains (lineParts [1 ], "fish" )) {
216
+ parsedProcessID , err := strconv .Atoi (lineParts [0 ])
217
+ if err == nil {
218
+ processOutputs = append (processOutputs , ProcessOutput {
219
+ processID : parsedProcessID ,
220
+ output : lineParts [1 ],
221
+ })
222
+ }
223
+ }
224
+ }
225
+ if len (processOutputs ) > 0 {
226
+ return processOutputs [0 ].output
227
+ }
228
+ return ""
229
+ }
0 commit comments