|
25 | 25 | #include <system_error> |
26 | 26 | #include <thread> |
27 | 27 | #include <utility> |
| 28 | +#include <optional> |
| 29 | +#include <algorithm> |
28 | 30 |
|
29 | 31 | #ifndef _WIN32 |
30 | 32 | #include <signal.h> |
@@ -124,19 +126,20 @@ namespace vix::commands::RunCommand::dev |
124 | 126 | continue; |
125 | 127 | } |
126 | 128 |
|
127 | | - const fs::path exePath = executable_path(); |
| 129 | + const std::optional<fs::path> exePath = executable_path(); |
128 | 130 |
|
129 | | - if (!fs::exists(exePath)) |
| 131 | + if (!exePath) |
130 | 132 | { |
131 | 133 | result.exitCode = 1; |
132 | | - result.message = "Dev executable not found: " + exePath.string(); |
| 134 | + result.message = "Dev executable not found for target: " + options_.targetName; |
133 | 135 |
|
134 | 136 | error(result.message); |
135 | | - hint("Make sure your CMakeLists.txt defines an executable named '" + options_.targetName + "'."); |
| 137 | + hint("Build directory: " + options_.buildDir.string()); |
| 138 | + hint("Make sure your CMakeLists.txt defines an executable target named '" + options_.targetName + "'."); |
136 | 139 | return result; |
137 | 140 | } |
138 | 141 |
|
139 | | - const int runCode = run_child_once(exePath); |
| 142 | + const int runCode = run_child_once(*exePath); |
140 | 143 |
|
141 | 144 | if (runCode != 0) |
142 | 145 | { |
@@ -308,11 +311,111 @@ namespace vix::commands::RunCommand::dev |
308 | 311 | return 0; |
309 | 312 | } |
310 | 313 |
|
311 | | - fs::path DevSession::executable_path() const |
| 314 | + std::optional<fs::path> DevSession::executable_path() const |
312 | 315 | { |
313 | | - return options_.buildDir / executable_name(options_.targetName); |
314 | | - } |
| 316 | + const std::string exeName = executable_name(options_.targetName); |
| 317 | + |
| 318 | + auto is_executable_candidate = [](const fs::path &path) -> bool |
| 319 | + { |
| 320 | + std::error_code ec; |
| 321 | + |
| 322 | + if (!fs::is_regular_file(path, ec) || ec) |
| 323 | + return false; |
| 324 | + |
| 325 | +#ifdef _WIN32 |
| 326 | + return path.extension() == ".exe"; |
| 327 | +#else |
| 328 | + const auto perms = fs::status(path, ec).permissions(); |
| 329 | + if (ec) |
| 330 | + return false; |
| 331 | + |
| 332 | + using pr = fs::perms; |
| 333 | + |
| 334 | + return (perms & pr::owner_exec) != pr::none || |
| 335 | + (perms & pr::group_exec) != pr::none || |
| 336 | + (perms & pr::others_exec) != pr::none; |
| 337 | +#endif |
| 338 | + }; |
| 339 | + |
| 340 | + auto looks_like_test_binary = [](const fs::path &path) -> bool |
| 341 | + { |
| 342 | + const std::string name = path.filename().string(); |
| 343 | + |
| 344 | + return name.find("_test") != std::string::npos || |
| 345 | + name.find("_tests") != std::string::npos || |
| 346 | + name.rfind("test_", 0) == 0; |
| 347 | + }; |
315 | 348 |
|
| 349 | + const std::vector<fs::path> preferred = { |
| 350 | + options_.buildDir / exeName, |
| 351 | + options_.buildDir / "bin" / exeName, |
| 352 | + options_.buildDir / "src" / exeName}; |
| 353 | + |
| 354 | + for (const auto &path : preferred) |
| 355 | + { |
| 356 | + if (is_executable_candidate(path)) |
| 357 | + return path; |
| 358 | + } |
| 359 | + |
| 360 | + std::vector<fs::path> exactNameCandidates; |
| 361 | + std::vector<fs::path> otherCandidates; |
| 362 | + |
| 363 | + std::error_code ec; |
| 364 | + for (auto it = fs::recursive_directory_iterator( |
| 365 | + options_.buildDir, |
| 366 | + fs::directory_options::skip_permission_denied, |
| 367 | + ec); |
| 368 | + !ec && it != fs::recursive_directory_iterator(); |
| 369 | + ++it) |
| 370 | + { |
| 371 | + const fs::path path = it->path(); |
| 372 | + |
| 373 | + if (path.string().find("CMakeFiles") != std::string::npos) |
| 374 | + continue; |
| 375 | + |
| 376 | + if (!is_executable_candidate(path)) |
| 377 | + continue; |
| 378 | + |
| 379 | + if (looks_like_test_binary(path)) |
| 380 | + continue; |
| 381 | + |
| 382 | +#ifdef _WIN32 |
| 383 | + const std::string baseName = path.stem().string(); |
| 384 | +#else |
| 385 | + const std::string baseName = path.filename().string(); |
| 386 | +#endif |
| 387 | + |
| 388 | + if (baseName == options_.targetName) |
| 389 | + exactNameCandidates.push_back(path); |
| 390 | + else |
| 391 | + otherCandidates.push_back(path); |
| 392 | + } |
| 393 | + |
| 394 | + auto prefer_bin_path = [](const fs::path &a, const fs::path &b) -> bool |
| 395 | + { |
| 396 | + const bool aBin = a.string().find("/bin/") != std::string::npos || |
| 397 | + a.string().find("\\bin\\") != std::string::npos; |
| 398 | + |
| 399 | + const bool bBin = b.string().find("/bin/") != std::string::npos || |
| 400 | + b.string().find("\\bin\\") != std::string::npos; |
| 401 | + |
| 402 | + if (aBin != bBin) |
| 403 | + return aBin; |
| 404 | + |
| 405 | + return a.string().size() < b.string().size(); |
| 406 | + }; |
| 407 | + |
| 408 | + if (!exactNameCandidates.empty()) |
| 409 | + { |
| 410 | + std::sort(exactNameCandidates.begin(), exactNameCandidates.end(), prefer_bin_path); |
| 411 | + return exactNameCandidates.front(); |
| 412 | + } |
| 413 | + |
| 414 | + if (otherCandidates.size() == 1) |
| 415 | + return otherCandidates.front(); |
| 416 | + |
| 417 | + return std::nullopt; |
| 418 | + } |
316 | 419 | #ifndef _WIN32 |
317 | 420 | int DevSession::run_child_once(const fs::path &exePath) |
318 | 421 | { |
|
0 commit comments