diff --git a/src/clice.cc b/src/clice.cc index d31ab0967..ec8d5c9d1 100644 --- a/src/clice.cc +++ b/src/clice.cc @@ -87,17 +87,126 @@ struct CliOptions { DECO_CFG_END(); }; +auto search_in_path(std::string_view name) -> std::string { + const char* path_env = std::getenv("PATH"); + if(!path_env || name.empty()) { + return std::string(name); + } + +#ifdef _WIN32 + constexpr char path_sep = ';'; + + // Get PATHEXT or use default extensions + std::vector extensions; + const char* pathext_env = std::getenv("PATHEXT"); + if(pathext_env) { + std::string_view pathext_view(pathext_env); + size_t ext_start = 0; + while(ext_start < pathext_view.size()) { + size_t ext_end = pathext_view.find(';', ext_start); + if(ext_end == std::string_view::npos) { + ext_end = pathext_view.size(); + } + std::string_view ext = pathext_view.substr(ext_start, ext_end - ext_start); + if(!ext.empty()) { + extensions.emplace_back(ext); + } + ext_start = ext_end + 1; + } + } else { + extensions = {".exe", ".cmd", ".bat", ".com"}; + } + + // Check if name already has an extension + bool has_extension = name.find('.') != std::string_view::npos; + + auto is_executable = [](const std::filesystem::path& p) { + std::error_code ec; + auto status = std::filesystem::status(p, ec); + return !ec && std::filesystem::exists(status) && !std::filesystem::is_directory(status); + }; +#else + constexpr char path_sep = ':'; + auto is_executable = [](const std::filesystem::path& p) { + std::error_code ec; + auto status = std::filesystem::status(p, ec); + if(ec || !std::filesystem::exists(status) || std::filesystem::is_directory(status)) { + return false; + } + return (status.permissions() & (std::filesystem::perms::owner_exec | + std::filesystem::perms::group_exec | + std::filesystem::perms::others_exec)) != std::filesystem::perms::none; + }; +#endif + + std::string_view path_view(path_env); + size_t start = 0; + while(start < path_view.size()) { + size_t end = path_view.find(path_sep, start); + if(end == std::string_view::npos) { + end = path_view.size(); + } + + std::string_view dir = path_view.substr(start, end - start); + if(!dir.empty()) { +#ifdef _WIN32 + // Try the name as-is first + std::filesystem::path full_path = std::filesystem::path(dir) / name; + std::error_code ec; + auto canonical = std::filesystem::canonical(full_path, ec); + if(!ec && is_executable(canonical)) { + return canonical.string(); + } + + // If name doesn't have an extension, try each PATHEXT extension + if(!has_extension) { + for(const auto& ext : extensions) { + std::string name_with_ext = std::string(name) + ext; + full_path = std::filesystem::path(dir) / name_with_ext; + canonical = std::filesystem::canonical(full_path, ec); + if(!ec && is_executable(canonical)) { + return canonical.string(); + } + } + } +#else + std::filesystem::path full_path = std::filesystem::path(dir) / name; + std::error_code ec; + auto canonical = std::filesystem::canonical(full_path, ec); + if(!ec && is_executable(canonical)) { + return canonical.string(); + } +#endif + } + + start = end + 1; + } + + return std::string(name); +} + auto resolve_self_path(int argc, const char** argv) -> std::string { if(argc <= 0 || argv == nullptr || argv[0] == nullptr) { return "clice"; } + std::string_view arg0(argv[0]); std::error_code ec; - auto absolute = std::filesystem::absolute(argv[0], ec); - if(ec) { - return std::string(argv[0]); + + // If arg0 contains a path separator, treat it as a path (relative or absolute) + if(arg0.find('/') != std::string_view::npos || arg0.find('\\') != std::string_view::npos) { + auto absolute = std::filesystem::absolute(arg0, ec); + if(!ec) { + auto canonical = std::filesystem::weakly_canonical(absolute, ec); + if(!ec) { + return canonical.string(); + } + } + return std::string(arg0); } - return absolute.string(); + + // No path separator - search in PATH + return search_in_path(arg0); } auto build_options(const CliOptions& cli_options, int argc, const char** argv)