|
| 1 | +module Wasp.Util.System |
| 2 | + ( resolveExecNameIO, |
| 3 | + isSystemWindows, |
| 4 | + isSystemMacOS, |
| 5 | + ExecName, |
| 6 | + ) |
| 7 | +where |
| 8 | + |
| 9 | +import Control.Exception (throwIO) |
| 10 | +import Control.Monad.Extra (firstJustM) |
| 11 | +import StrongPath (Abs, File', Path', parseAbsFile) |
| 12 | +import System.Directory (findExecutable) |
| 13 | +import qualified System.Info |
| 14 | + |
| 15 | +-- | Executable name as expected by Haskell's "System.Process" and its 'System.Process.RawCommand', |
| 16 | +-- therefore suited for passing to their functions for creating/executing processes. |
| 17 | +-- It can be just "node", or "node.exe", or relative or full path, ... . |
| 18 | +type ExecName = FilePath |
| 19 | + |
| 20 | +-- | Resolve given executable name (e.g. "node") to the full executable path. |
| 21 | +-- |
| 22 | +-- Note that filename of the resolved path might not be exactly equal to the provided executable |
| 23 | +-- name, but may have additional extension (e.g. "node" might resolve to "/some/path/node.cmd"). |
| 24 | +-- |
| 25 | +-- The reason why we return resolved absolute executable path and not just the resolved filename |
| 26 | +-- (e.g. "node.cmd" for "node") is that, as per Haskell docs, when passing just the filename to |
| 27 | +-- 'System.Process.createProcess', we can get unexpected resolution if 'cwd' option is set. |
| 28 | +-- For example it can resolve to "npm.cmd" in the local ".node_modules" instead of a global one. |
| 29 | +-- So it is better to stick with absolute paths. |
| 30 | +-- |
| 31 | +-- Motivation for this function was mainly driven by how exec names are resolved when executing a |
| 32 | +-- process on Windows. |
| 33 | +-- On Linux/MacOS situation is simple, the system will normally do the name resolution for us, so |
| 34 | +-- e.g. if we pass "npm" to 'System.Process.proc', that will work out of the box. |
| 35 | +-- But on Windows, that will normally fail, since there is no "npm" really but instead "npm.cmd" or |
| 36 | +-- "npm.exe". In that case, we want to figure out what exactly is the right exec name to use. |
| 37 | +-- Note that we don't have to bother with this when using 'System.Process.shell' instead of |
| 38 | +-- 'System.Process.proc', becuase then the shell will do system resolution for us, |
| 39 | +-- but at the price of abandoning any argument escaping. |
| 40 | +-- |
| 41 | +-- Throws IOError if it failed to resolve the name. |
| 42 | +-- |
| 43 | +-- Example: resolveExecNameIO "npm" -> "C:\...\npm.cmd" |
| 44 | +resolveExecNameIO :: ExecName -> IO (Path' Abs File') |
| 45 | +resolveExecNameIO execName = do |
| 46 | + firstJustM findExecutable execNamesToLookForByPriorityDesc >>= \case |
| 47 | + Just execPath -> parseAbsFile execPath |
| 48 | + Nothing -> |
| 49 | + (throwIO . userError . unlines) |
| 50 | + [ "Could not find '" <> execName <> "' executable on your system.", |
| 51 | + "Please ensure " <> execName <> " is installed and available in your PATH." |
| 52 | + ] |
| 53 | + where |
| 54 | + execNamesToLookForByPriorityDesc |
| 55 | + | isSystemWindows = (execName <>) <$> ["", ".cmd", ".exe", ".ps1"] |
| 56 | + | otherwise = [execName] |
| 57 | + |
| 58 | +isSystemWindows :: Bool |
| 59 | +isSystemWindows = System.Info.os == "mingw32" |
| 60 | + |
| 61 | +isSystemMacOS :: Bool |
| 62 | +isSystemMacOS = System.Info.os == "darwin" |
0 commit comments