tint: Make Command::LookPath also look in the executable directory

Added a utility to retrieve the executable path and directory, and make
Command::LookPath also look in there if not found in the CWD, and before
looking in the path environment variable. This should make it a lot
easier to run external executables and dlls as won't have to make sure
to set the path each time.

Change-Id: I09d6ff0a07f1a7904787d4be2a4abdf119a19ad6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/212914
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/utils/command/BUILD.bazel b/src/tint/utils/command/BUILD.bazel
index ffcef85..f321564 100644
--- a/src/tint/utils/command/BUILD.bazel
+++ b/src/tint/utils/command/BUILD.bazel
@@ -60,6 +60,7 @@
   ],
   deps = [
     "//src/tint/utils/macros",
+    "//src/tint/utils/system",
     "//src/tint/utils/text",
   ],
   copts = COPTS,
diff --git a/src/tint/utils/command/BUILD.cmake b/src/tint/utils/command/BUILD.cmake
index 4e40cc0..c05d974 100644
--- a/src/tint/utils/command/BUILD.cmake
+++ b/src/tint/utils/command/BUILD.cmake
@@ -44,6 +44,7 @@
 
 tint_target_add_dependencies(tint_utils_command lib
   tint_utils_macros
+  tint_utils_system
   tint_utils_text
 )
 
diff --git a/src/tint/utils/command/BUILD.gn b/src/tint/utils/command/BUILD.gn
index 67d6e4c..6650a54 100644
--- a/src/tint/utils/command/BUILD.gn
+++ b/src/tint/utils/command/BUILD.gn
@@ -47,6 +47,7 @@
   sources = [ "command.h" ]
   deps = [
     "${tint_src_dir}/utils/macros",
+    "${tint_src_dir}/utils/system",
     "${tint_src_dir}/utils/text",
   ]
 
diff --git a/src/tint/utils/command/command.h b/src/tint/utils/command/command.h
index 8fc7e00..f38c93c 100644
--- a/src/tint/utils/command/command.h
+++ b/src/tint/utils/command/command.h
@@ -52,9 +52,9 @@
     /// @param path path to the executable
     explicit Command(const std::string& path);
 
-    /// Looks for an executable with the given name in the current working
-    /// directory, and if not found there, in each of the directories in the
-    /// `PATH` environment variable.
+    /// Looks for an executable with the given name in the current working directory,
+    /// then in the executable directory if not found there, then in each of the
+    /// directories in the `PATH` environment variable.
     /// @param executable the executable name
     /// @returns a Command which will return true for Found() if the executable
     /// was found.
diff --git a/src/tint/utils/command/command_posix.cc b/src/tint/utils/command/command_posix.cc
index fae21be..e52632b 100644
--- a/src/tint/utils/command/command_posix.cc
+++ b/src/tint/utils/command/command_posix.cc
@@ -37,6 +37,8 @@
 #include <sstream>
 #include <vector>
 
+#include "src/tint/utils/system/executable_path.h"
+
 namespace tint {
 
 namespace {
@@ -132,6 +134,10 @@
         if (ExecutableExists(in_cwd)) {
             return in_cwd;
         }
+        auto in_exe_path = tint::ExecutableDirectory() + "/" + name;
+        if (ExecutableExists(in_exe_path)) {
+            return in_exe_path;
+        }
     }
     if (ExecutableExists(name)) {
         return name;
diff --git a/src/tint/utils/command/command_windows.cc b/src/tint/utils/command/command_windows.cc
index e591416..bf875d8 100644
--- a/src/tint/utils/command/command_windows.cc
+++ b/src/tint/utils/command/command_windows.cc
@@ -35,6 +35,7 @@
 #include <string>
 
 #include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/system/executable_path.h"
 #include "src/tint/utils/text/string_stream.h"
 
 namespace tint {
@@ -159,12 +160,22 @@
     if (ExecutableExists(in_cwd + ".exe")) {
         return in_cwd + ".exe";
     }
+
+    auto in_exe_path = tint::ExecutableDirectory() + "/" + name;
+    if (ExecutableExists(in_exe_path)) {
+        return in_exe_path;
+    }
+    if (ExecutableExists(in_exe_path + ".exe")) {
+        return in_exe_path + ".exe";
+    }
+
     if (ExecutableExists(name)) {
         return name;
     }
     if (ExecutableExists(name + ".exe")) {
         return name + ".exe";
     }
+
     if (name.find("/") == std::string::npos && name.find("\\") == std::string::npos) {
         char* path_env = nullptr;
         size_t path_env_len = 0;
diff --git a/src/tint/utils/system/BUILD.bazel b/src/tint/utils/system/BUILD.bazel
index 75861b6..f2f23b7 100644
--- a/src/tint/utils/system/BUILD.bazel
+++ b/src/tint/utils/system/BUILD.bazel
@@ -50,19 +50,31 @@
     ],
     "//conditions:default": [],
   }) + select({
+    ":tint_build_is_linux": [
+      "executable_path_linux.cc",
+    ],
+    "//conditions:default": [],
+  }) + select({
     ":tint_build_is_linux_or_tint_build_is_mac": [
       "terminal_posix.cc",
     ],
     "//conditions:default": [],
   }) + select({
+    ":tint_build_is_mac": [
+      "executable_file_mac.cc",
+    ],
+    "//conditions:default": [],
+  }) + select({
     ":tint_build_is_win": [
       "env_windows.cc",
+      "executable_path_windows.cc",
       "terminal_windows.cc",
     ],
     "//conditions:default": [],
   }),
   hdrs = [
     "env.h",
+    "executable_path.h",
     "terminal.h",
   ],
   deps = [
diff --git a/src/tint/utils/system/BUILD.cmake b/src/tint/utils/system/BUILD.cmake
index 62570e0..770cbf4 100644
--- a/src/tint/utils/system/BUILD.cmake
+++ b/src/tint/utils/system/BUILD.cmake
@@ -40,6 +40,7 @@
 ################################################################################
 tint_add_target(tint_utils_system lib
   utils/system/env.h
+  utils/system/executable_path.h
   utils/system/terminal.h
 )
 
@@ -69,15 +70,28 @@
   )
 endif((NOT TINT_BUILD_IS_WIN))
 
+if(TINT_BUILD_IS_LINUX)
+  tint_target_add_sources(tint_utils_system lib
+    "utils/system/executable_path_linux.cc"
+  )
+endif(TINT_BUILD_IS_LINUX)
+
 if(TINT_BUILD_IS_LINUX OR TINT_BUILD_IS_MAC)
   tint_target_add_sources(tint_utils_system lib
     "utils/system/terminal_posix.cc"
   )
 endif(TINT_BUILD_IS_LINUX OR TINT_BUILD_IS_MAC)
 
+if(TINT_BUILD_IS_MAC)
+  tint_target_add_sources(tint_utils_system lib
+    "utils/system/executable_file_mac.cc"
+  )
+endif(TINT_BUILD_IS_MAC)
+
 if(TINT_BUILD_IS_WIN)
   tint_target_add_sources(tint_utils_system lib
     "utils/system/env_windows.cc"
+    "utils/system/executable_path_windows.cc"
     "utils/system/terminal_windows.cc"
   )
 endif(TINT_BUILD_IS_WIN)
diff --git a/src/tint/utils/system/BUILD.gn b/src/tint/utils/system/BUILD.gn
index 19a3f5f..1148997 100644
--- a/src/tint/utils/system/BUILD.gn
+++ b/src/tint/utils/system/BUILD.gn
@@ -42,6 +42,7 @@
 libtint_source_set("system") {
   sources = [
     "env.h",
+    "executable_path.h",
     "terminal.h",
   ]
   deps = [
@@ -63,13 +64,22 @@
     sources += [ "env_other.cc" ]
   }
 
+  if (tint_build_is_linux) {
+    sources += [ "executable_path_linux.cc" ]
+  }
+
   if (tint_build_is_linux || tint_build_is_mac) {
     sources += [ "terminal_posix.cc" ]
   }
 
+  if (tint_build_is_mac) {
+    sources += [ "executable_file_mac.cc" ]
+  }
+
   if (tint_build_is_win) {
     sources += [
       "env_windows.cc",
+      "executable_path_windows.cc",
       "terminal_windows.cc",
     ]
   }
diff --git a/src/tint/utils/system/executable_file_mac.cc b/src/tint/utils/system/executable_file_mac.cc
new file mode 100644
index 0000000..928d285
--- /dev/null
+++ b/src/tint/utils/system/executable_file_mac.cc
@@ -0,0 +1,50 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(tint_build_is_mac)
+
+#include "src/tint/utils/system/executable_path.h"
+
+#include <mach-o/dyld.h>
+#include <sys/syslimits.h>
+#include <filesystem>
+
+#include "src/tint/utils/containers/vector.h"
+
+std::string tint::ExecutablePath() {
+    uint32_t bufsize = 0;
+    auto result = _NSGetExecutablePath(nullptr, &bufsize);
+    TINT_ASSERT(result == -1 && bufsize > 0);
+    tint::Vector<char, PATH_MAX> buff;
+    buff.Resize(bufsize + 1, 0);
+    _NSGetExecutablePath(&buff[0], &bufsize);
+    return &buff[0];
+}
+
+std::string tint::ExecutableDirectory() {
+    return std::filesystem::path{ExecutablePath()}.remove_filename().string();
+}
diff --git a/src/tint/utils/system/executable_path.h b/src/tint/utils/system/executable_path.h
new file mode 100644
index 0000000..bb04d30
--- /dev/null
+++ b/src/tint/utils/system/executable_path.h
@@ -0,0 +1,43 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_UTILS_SYSTEM_EXECUTABLE_PATH_H_
+#define SRC_TINT_UTILS_SYSTEM_EXECUTABLE_PATH_H_
+
+#include <string>
+
+namespace tint {
+
+// Returns the path of the currently running executable
+std::string ExecutablePath();
+
+// Returns the directory of the currently running executable
+std::string ExecutableDirectory();
+
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_SYSTEM_EXECUTABLE_PATH_H_
diff --git a/src/tint/utils/system/executable_path_linux.cc b/src/tint/utils/system/executable_path_linux.cc
new file mode 100644
index 0000000..fad508f
--- /dev/null
+++ b/src/tint/utils/system/executable_path_linux.cc
@@ -0,0 +1,53 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(tint_build_is_linux)
+
+#include "src/tint/utils/system/executable_path.h"
+
+#include <unistd.h>
+#include <filesystem>
+
+#include "src/tint/utils/containers/vector.h"
+
+std::string tint::ExecutablePath() {
+    tint::Vector<char, 1024> buff;
+    buff.Resize(1024, 0);
+    while (true) {
+        ssize_t result = readlink("/proc/self/exe", &buff[0], buff.Length());
+        if (result == static_cast<ssize_t>(buff.Length())) {
+            buff.Resize(buff.Length() * 2, 0);
+        } else {
+            break;
+        }
+    }
+    return &buff[0];
+}
+
+std::string tint::ExecutableDirectory() {
+    return std::filesystem::path{ExecutablePath()}.remove_filename().string();
+}
diff --git a/src/tint/utils/system/executable_path_windows.cc b/src/tint/utils/system/executable_path_windows.cc
new file mode 100644
index 0000000..a9cea85
--- /dev/null
+++ b/src/tint/utils/system/executable_path_windows.cc
@@ -0,0 +1,53 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(tint_build_is_win)
+
+#include "src/tint/utils/system/executable_path.h"
+
+#include <Windows.h>
+#include <filesystem>
+
+#include "src/tint/utils/containers/vector.h"
+
+std::string tint::ExecutablePath() {
+    tint::Vector<char, MAX_PATH> buff;
+    buff.Resize(MAX_PATH);
+    while (true) {
+        auto result = ::GetModuleFileNameA(NULL, &buff[0], buff.Length());
+        if (result == buff.Length() && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
+            buff.Resize(buff.Length() * 2);
+        } else {
+            break;
+        }
+    }
+    return &buff[0];
+}
+
+std::string tint::ExecutableDirectory() {
+    return std::filesystem::path{ExecutablePath()}.remove_filename().string();
+}