1919#include < vix/cli/manifest/RunManifestMerge.hpp>
2020#include < vix/cli/manifest/VixManifest.hpp>
2121#include < vix/cli/app/AppProjectResolver.hpp>
22+ #include < vix/cli/commands/run/detail/RunnableExecutableResolver.hpp>
2223#include < vix/cli/Style.hpp>
2324#include < vix/utils/Env.hpp>
2425
@@ -177,130 +178,6 @@ namespace
177178 return out;
178179 }
179180
180- static std::vector<fs::path> find_runnable_executables (
181- const fs::path &buildDir,
182- bool includeTests = false )
183- {
184- std::vector<fs::path> candidates;
185-
186- auto is_executable_candidate = [](const fs::path &p) -> bool
187- {
188- std::error_code ec{};
189-
190- if (!fs::is_regular_file (p, ec) || ec)
191- return false ;
192-
193- #ifdef _WIN32
194- return p.extension () == " .exe" ;
195- #else
196- const auto perms = fs::status (p, ec).permissions ();
197- if (ec)
198- return false ;
199-
200- using pr = fs::perms;
201- return (perms & pr::owner_exec) != pr::none ||
202- (perms & pr::group_exec) != pr::none ||
203- (perms & pr::others_exec) != pr::none;
204- #endif
205- };
206-
207- auto looks_like_test_binary = [](const fs::path &p) -> bool
208- {
209- const std::string n = p.filename ().string ();
210- return n.find (" _test" ) != std::string::npos ||
211- n.find (" _tests" ) != std::string::npos ||
212- n.rfind (" test_" , 0 ) == 0 ;
213- };
214-
215- std::error_code ec{};
216- if (!fs::exists (buildDir, ec) || ec)
217- return candidates;
218-
219- for (auto it = fs::recursive_directory_iterator (
220- buildDir,
221- fs::directory_options::skip_permission_denied,
222- ec);
223- !ec && it != fs::recursive_directory_iterator ();
224- ++it)
225- {
226- const fs::path p = it->path ();
227-
228- const std::string pathString = p.string ();
229-
230- if (pathString.find (" CMakeFiles" ) != std::string::npos)
231- continue ;
232-
233- if (pathString.find (" .vix" ) != std::string::npos)
234- continue ;
235-
236- if (!is_executable_candidate (p))
237- continue ;
238-
239- if (!includeTests && looks_like_test_binary (p))
240- continue ;
241-
242- candidates.push_back (p);
243- }
244-
245- auto prefer_short_bin_path = [](const fs::path &a, const fs::path &b) -> bool
246- {
247- const bool aBin = a.string ().find (" /bin/" ) != std::string::npos ||
248- a.string ().find (" \\ bin\\ " ) != std::string::npos;
249-
250- const bool bBin = b.string ().find (" /bin/" ) != std::string::npos ||
251- b.string ().find (" \\ bin\\ " ) != std::string::npos;
252-
253- if (aBin != bBin)
254- return aBin;
255-
256- return a.string ().size () < b.string ().size ();
257- };
258-
259- std::sort (candidates.begin (), candidates.end (), prefer_short_bin_path);
260- return candidates;
261- }
262-
263- static std::optional<fs::path> resolve_runnable_executable (
264- const fs::path &buildDir,
265- const std::string &preferredName = {})
266- {
267- const auto candidates = find_runnable_executables (buildDir, false );
268-
269- if (!preferredName.empty ())
270- {
271- for (const auto &p : candidates)
272- {
273- #ifdef _WIN32
274- const std::string name = p.stem ().string ();
275- #else
276- const std::string name = p.filename ().string ();
277- #endif
278-
279- if (name == preferredName)
280- return p;
281- }
282- }
283-
284- if (candidates.size () == 1 )
285- return candidates.front ();
286-
287- return std::nullopt ;
288- }
289-
290- static void print_runnable_executable_candidates (
291- const fs::path &buildDir)
292- {
293- const auto candidates = find_runnable_executables (buildDir, false );
294-
295- if (candidates.empty ())
296- return ;
297-
298- hint (" Runnable executables found:" );
299-
300- for (const auto &p : candidates)
301- step (" " + p.filename ().string ());
302- }
303-
304181 static int build_project_with_vix_build (
305182 const fs::path &projectDir,
306183 const Options &opt,
@@ -382,7 +259,7 @@ namespace
382259 return buildExit;
383260
384261 auto exePath =
385- resolve_runnable_executable (
262+ detail:: resolve_runnable_executable (
386263 buildDir,
387264 requestedTarget);
388265
@@ -392,11 +269,11 @@ namespace
392269 {
393270 error (" Built executable not found for target: " + requestedTarget);
394271 hint (" Resolved build directory: " + buildDir.string ());
395- print_runnable_executable_candidates (buildDir);
272+ detail:: print_runnable_executable_candidates (buildDir);
396273 return 1 ;
397274 }
398275
399- const auto candidates = find_runnable_executables (buildDir, false );
276+ const auto candidates = detail:: find_runnable_executables (buildDir, false );
400277
401278 if (candidates.empty ())
402279 {
@@ -410,7 +287,7 @@ namespace
410287 hint (" Run one explicitly:" );
411288
412289 for (const auto &p : candidates)
413- step (" vix run " + p. filename (). string ( ));
290+ step (" vix run " + detail::runnable_executable_display_path (p ));
414291
415292 return 1 ;
416293 }
@@ -1145,6 +1022,28 @@ namespace
11451022
11461023 if (!exePathOpt)
11471024 {
1025+ const auto candidates = find_runnable_executables (buildDir, false );
1026+
1027+ if (candidates.size () > 1 )
1028+ {
1029+ error (" Multiple runnable executables found." );
1030+ hint (" Resolved build directory: " + buildDir.string ());
1031+ hint (" Run one explicitly:" );
1032+
1033+ for (const auto &p : candidates)
1034+ {
1035+ std::error_code relEc;
1036+ fs::path rel = fs::relative (p, fs::current_path (), relEc);
1037+
1038+ const std::string runnable =
1039+ relEc ? p.string () : rel.string ();
1040+
1041+ step (" vix run " + runnable);
1042+ }
1043+
1044+ return 1 ;
1045+ }
1046+
11481047 const int testRc = run_test_binary_if_present (
11491048 buildDir,
11501049 opt.runArgs ,
@@ -1165,14 +1064,15 @@ namespace
11651064 error (" Built executable not found for target: " + exeName);
11661065 else
11671066 error (" Built executable not found." );
1067+
11681068 hint (" Resolved build directory: " + buildDir.string ());
1069+ hint (" No runnable application target could be resolved automatically." );
11691070
11701071 if (resolved.generated )
11711072 hint (" Generated CMake source: " + resolved.cmakeSourceDir .string ());
11721073
11731074 return 1 ;
11741075 }
1175-
11761076 const int runRc = run_executable_direct (
11771077 *exePathOpt,
11781078 opt,
@@ -1358,11 +1258,37 @@ namespace
13581258
13591259 if (!exePathOpt)
13601260 {
1361- error (" Built executable not found for project: " + exeName);
1362- hint (" Resolved build directory: " + buildDir.string ());
1363- hint (" No runnable application target could be resolved automatically." );
1364- hint (" If your executable uses a custom output path or custom target name, add a manifest field to specify it." );
1365- return 1 ;
1261+ const auto candidates = find_runnable_executables (buildDir, false );
1262+
1263+ if (candidates.empty ())
1264+ {
1265+ error (" Built executable not found." );
1266+ hint (" Resolved build directory: " + buildDir.string ());
1267+ hint (" No runnable application target could be resolved automatically." );
1268+ return 1 ;
1269+ }
1270+
1271+ if (candidates.size () > 1 )
1272+ {
1273+ error (" Multiple runnable executables found." );
1274+ hint (" Resolved build directory: " + buildDir.string ());
1275+ hint (" Run one explicitly:" );
1276+
1277+ for (const auto &p : candidates)
1278+ {
1279+ std::error_code ec;
1280+ fs::path rel = fs::relative (p, fs::current_path (), ec);
1281+
1282+ const std::string runnable =
1283+ ec ? p.string () : rel.string ();
1284+
1285+ step (" vix run " + runnable);
1286+ }
1287+
1288+ return 1 ;
1289+ }
1290+
1291+ exePathOpt = candidates.front ();
13661292 }
13671293
13681294 fs::path exePath = *exePathOpt;
0 commit comments