Skip to content

Commit bb4ebb3

Browse files
committed
feat(check): overhaul vix check with robust profiles, global packages, and improved runtime validation
- introduce isolated build directories per profile (default, san, ubsan) - add global packages support in check (parity with vix build/run) - fix sanitizer fallback configure to avoid cache reuse bugs - auto-enable runtime validation for --san / --ubsan - improve runtime detection (server timeout != failure if healthy) - enhance CLI output using Ui helpers (sections, kv, status lines) - support full option parsing (--*=value, repeated args, etc.) - improve script mode with profile-based builds and signatures - refine error handling and diagnostics consistency vix check is now deterministic, reliable, and production-ready
1 parent 35fd29b commit bb4ebb3

File tree

5 files changed

+1445
-717
lines changed

5 files changed

+1445
-717
lines changed

include/vix/cli/commands/check/CheckDetail.hpp

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,126 @@ namespace vix::commands::CheckCommand::detail
2121
{
2222
namespace fs = std::filesystem;
2323

24+
/**
25+
* @brief Parsed options for `vix check`.
26+
*
27+
* This structure is shared by both project mode and script mode.
28+
*
29+
* Project mode:
30+
* - configure
31+
* - build
32+
* - optional tests
33+
* - optional runtime validation
34+
*
35+
* Script mode:
36+
* - temporary configure
37+
* - build
38+
* - optional runtime validation when sanitizers are enabled
39+
*/
2440
struct Options
2541
{
26-
// common
42+
/**
43+
* @name Common options
44+
* @{
45+
*/
46+
47+
/// Explicit project directory passed with --dir.
2748
std::string dir;
49+
50+
/// Configure preset name used in project mode.
2851
std::string preset = "dev-ninja";
29-
std::string buildPreset; // optional (override)
52+
53+
/// Optional build preset override.
54+
std::string buildPreset;
55+
56+
/// Number of parallel jobs for the build step.
3057
int jobs = 0;
58+
59+
/// Minimal output mode.
3160
bool quiet = false;
61+
62+
/// More verbose output mode.
3263
bool verbose = false;
64+
65+
/// Optional log level for the current check session.
3366
std::string logLevel;
3467

35-
// script mode
68+
/** @} */
69+
70+
/**
71+
* @name Script mode
72+
* @{
73+
*/
74+
75+
/// True when the user passed a single .cpp file.
3676
bool singleCpp = false;
77+
78+
/// Absolute path of the .cpp file to validate.
3779
fs::path cppFile;
38-
bool enableSanitizers = false; // --san
39-
bool enableUbsanOnly = false; // --ubsan
4080

41-
// project checks
42-
bool tests = false; // --tests
43-
std::string ctestPreset; // --ctest-preset
44-
std::vector<std::string> ctestArgs; // repeatable: --ctest-arg <...>
81+
/// Enable AddressSanitizer + UBSan.
82+
bool enableSanitizers = false;
83+
84+
/// Enable UBSan only.
85+
bool enableUbsanOnly = false;
4586

46-
// runtime check (project mode + optional)
47-
bool runAfterBuild = false; // --run
48-
int runTimeoutSec = 0; // --run-timeout <sec> (0 = no timeout)
87+
/** @} */
88+
89+
/**
90+
* @name Project test options
91+
* @{
92+
*/
93+
94+
/// Run tests after build.
95+
bool tests = false;
96+
97+
/// Optional CTest preset override.
98+
std::string ctestPreset;
99+
100+
/// Extra arguments forwarded to CTest.
101+
std::vector<std::string> ctestArgs;
102+
103+
/** @} */
104+
105+
/**
106+
* @name Runtime validation
107+
* @{
108+
*/
109+
110+
/// Run the built executable after build.
111+
bool runAfterBuild = false;
112+
113+
/// Runtime timeout in seconds. 0 means no explicit timeout override.
114+
int runTimeoutSec = 0;
115+
116+
/** @} */
49117
};
50118

119+
/**
120+
* @brief Parse CLI arguments for `vix check`.
121+
*
122+
* @param args Raw command arguments.
123+
* @return Parsed options.
124+
*/
51125
Options parse(const std::vector<std::string> &args);
52126

53-
// script mode
127+
/**
128+
* @brief Validate a single C++ script in temporary project mode.
129+
*
130+
* @param opt Parsed options.
131+
* @return Process exit code.
132+
*/
54133
int check_single_cpp(const Options &opt);
55-
// project mode
134+
135+
/**
136+
* @brief Validate a CMake project.
137+
*
138+
* @param opt Parsed options.
139+
* @param projectDir Resolved project directory.
140+
* @return Process exit code.
141+
*/
56142
int check_project(const Options &opt, const fs::path &projectDir);
57143

58144
} // namespace vix::commands::CheckCommand::detail
59145

60-
#endif
146+
#endif // VIX_CHECK_DETAIL_HPP

src/commands/CheckCommand.cpp

Lines changed: 117 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -12,79 +12,112 @@
1212
*
1313
*/
1414
#include <vix/cli/commands/CheckCommand.hpp>
15-
#include <vix/cli/Style.hpp>
16-
1715
#include <vix/cli/commands/check/CheckDetail.hpp>
16+
#include <vix/cli/Style.hpp>
17+
#include <vix/cli/util/Ui.hpp>
1818

1919
#include <filesystem>
2020
#include <iostream>
21-
22-
using namespace vix::cli::style;
23-
namespace fs = std::filesystem;
21+
#include <system_error>
22+
#include <vector>
2423

2524
namespace vix::commands::CheckCommand
2625
{
27-
using namespace detail;
26+
namespace fs = std::filesystem;
27+
namespace ui = vix::cli::util;
28+
namespace style = vix::cli::style;
2829

29-
static bool exists_cmake_project(const fs::path &p)
30-
{
31-
std::error_code ec;
32-
return fs::exists(p / "CMakeLists.txt", ec);
33-
}
30+
using namespace detail;
3431

35-
static fs::path resolve_project_dir_or_empty(const Options &opt)
32+
namespace
3633
{
37-
const fs::path cwd = fs::current_path();
34+
static bool exists_cmake_project(const fs::path &p)
35+
{
36+
std::error_code ec;
37+
return fs::exists(p / "CMakeLists.txt", ec);
38+
}
3839

39-
if (!opt.dir.empty())
40+
static fs::path resolve_project_dir_or_empty(const Options &opt)
4041
{
41-
fs::path d = fs::path(opt.dir);
42-
if (exists_cmake_project(d))
43-
return d;
42+
const fs::path cwd = fs::current_path();
43+
44+
if (!opt.dir.empty())
45+
{
46+
const fs::path d = fs::path(opt.dir);
47+
if (exists_cmake_project(d))
48+
return d;
49+
return {};
50+
}
51+
52+
if (exists_cmake_project(cwd))
53+
return cwd;
54+
55+
fs::path cur = cwd;
56+
for (int i = 0; i < 6; ++i)
57+
{
58+
if (exists_cmake_project(cur))
59+
return cur;
60+
61+
if (!cur.has_parent_path())
62+
break;
63+
64+
const fs::path parent = cur.parent_path();
65+
if (parent == cur)
66+
break;
67+
68+
cur = parent;
69+
}
70+
4471
return {};
4572
}
4673

47-
if (exists_cmake_project(cwd))
48-
return cwd;
49-
50-
fs::path cur = cwd;
51-
for (int i = 0; i < 6; ++i)
74+
static void print_project_resolution(const Options &opt, const fs::path &projectDir)
5275
{
53-
if (exists_cmake_project(cur))
54-
return cur;
76+
if (opt.quiet)
77+
return;
5578

56-
if (!cur.has_parent_path())
57-
break;
79+
ui::section(std::cout, "Check");
80+
ui::kv(std::cout, "mode", "project");
81+
ui::kv(std::cout, "project dir", projectDir.string());
82+
ui::one_line_spacer(std::cout);
83+
}
5884

59-
fs::path parent = cur.parent_path();
60-
if (parent == cur)
61-
break;
85+
static void print_script_resolution(const Options &opt, const fs::path &scriptPath)
86+
{
87+
if (opt.quiet)
88+
return;
6289

63-
cur = parent;
90+
ui::section(std::cout, "Check");
91+
ui::kv(std::cout, "mode", "script");
92+
ui::kv(std::cout, "script", scriptPath.string());
93+
ui::one_line_spacer(std::cout);
6494
}
6595

66-
return {};
67-
}
96+
static void print_help_section_header(std::ostream &out, const std::string &title)
97+
{
98+
out << title << ":\n";
99+
}
100+
} // namespace
68101

69102
int run(const std::vector<std::string> &args)
70103
{
71104
const Options opt = parse(args);
72105

73106
if (opt.singleCpp)
107+
{
108+
print_script_resolution(opt, opt.cppFile);
74109
return detail::check_single_cpp(opt);
110+
}
75111

76112
const fs::path projectDir = resolve_project_dir_or_empty(opt);
77-
78113
if (projectDir.empty())
79114
{
80-
error("Unable to determine the project folder.");
81-
hint("Try: vix check --dir <path> or run from a CMake project directory.");
115+
style::error("Unable to determine the project folder.");
116+
style::hint("Try: vix check --dir <path> or run from a CMake project directory.");
82117
return 1;
83118
}
84119

85-
info("Using project directory:");
86-
step(projectDir.string());
87-
120+
print_project_resolution(opt, projectDir);
88121
return detail::check_project(opt, projectDir);
89122
}
90123

@@ -95,51 +128,60 @@ namespace vix::commands::CheckCommand
95128
out << "Usage:\n";
96129
out << " vix check [path|file.cpp] [options]\n\n";
97130

98-
out << "Description:\n";
131+
print_help_section_header(out, "Description");
99132
out << " Validate a Vix/CMake project or a single-file C++ script.\n\n";
100133

101-
out << " Project mode:\n";
102-
out << " - Configure (via CMake presets)\n";
103-
out << " - Build the project\n";
104-
out << " - Optional: run tests (CTest)\n";
105-
out << " - Optional: run the built binary (runtime check)\n\n";
134+
print_help_section_header(out, "Project mode");
135+
out << " - Configure the project\n";
136+
out << " - Build the project\n";
137+
out << " - Optional: run tests with CTest\n";
138+
out << " - Optional: run the built executable\n";
139+
out << " - With --san / --ubsan, use an isolated check profile\n\n";
106140

107-
out << " Script mode (.cpp file):\n";
108-
out << " - Configure a temporary project\n";
109-
out << " - Compile the file\n";
110-
out << " - No execution (use `vix run` for execution)\n\n";
141+
print_help_section_header(out, "Script mode");
142+
out << " - Create a temporary CMake project\n";
143+
out << " - Compile the file\n";
144+
out << " - With sanitizers enabled, also run the binary for runtime validation\n\n";
111145

112-
out << "Options:\n";
113-
out << " -d, --dir <path> Explicit project directory\n";
146+
print_help_section_header(out, "Options");
147+
out << " -d, --dir <path> Explicit project directory\n";
114148
out << " --preset <name> Configure preset (default: dev-ninja)\n";
115-
out << " --build-preset <name> Build preset override (optional)\n";
116-
out << " -j, --jobs <n> Number of parallel build jobs\n\n";
117-
118-
out << " --tests Run CTest after build (project mode)\n";
119-
out << " --ctest-preset <name> CTest preset to use (optional)\n";
120-
out << " --run Run the built binary after build\n\n";
121-
122-
out << "Sanitizers:\n";
149+
out << " --build-preset <name> Build preset override\n";
150+
out << " -j, --jobs <n> Number of parallel build jobs\n";
151+
out << " --tests Run CTest after build\n";
152+
out << " --ctest-preset <name> CTest preset override\n";
153+
out << " --ctest-arg <arg> Extra argument forwarded to CTest (repeatable)\n";
154+
out << " --run Run the built executable after build\n";
155+
out << " --run-timeout <sec> Runtime timeout in seconds\n";
156+
out << " --quiet, -q Minimal output\n";
157+
out << " --verbose More verbose logging\n";
158+
out << " --log-level <level> Set VIX_LOG_LEVEL for the check session\n\n";
159+
160+
print_help_section_header(out, "Sanitizers");
123161
out << " --san Enable AddressSanitizer + UBSan\n";
124162
out << " --ubsan Enable UBSan only\n\n";
125163

126-
out << "Notes:\n";
127-
out << " - In project mode, sanitizers are applied via presets\n";
128-
out << " (e.g. dev-ninja-san, dev-ninja-ubsan)\n";
129-
out << " - In script mode, sanitizers affect compilation only\n\n";
130-
131-
out << "Examples:\n";
132-
out << " vix check Check current project (configure + build)\n";
133-
out << " vix check --tests Build project and run CTest\n";
134-
out << " vix check --run Build project and run the compiled binary\n";
135-
out << " vix check --tests --run Full validation: build, tests, runtime\n";
136-
out << " vix check --san --tests --run Full validation with ASan + UBSan enabled\n\n";
137-
138-
out << " vix check --dir ./examples/api Check a project located in a specific directory\n\n";
139-
140-
out << " vix check main.cpp Compile a single C++ file (script mode)\n";
141-
out << " vix check main.cpp --san Compile a script with ASan + UBSan\n";
142-
out << " vix check main.cpp --ubsan Compile a script with UBSan only\n";
164+
print_help_section_header(out, "Notes");
165+
out << " - Project checks use isolated build directories per profile.\n";
166+
out << " Example: build-ninja, build-ninja-san, build-ninja-ubsan.\n";
167+
out << " - If a dedicated sanitizer preset does not exist, Vix falls back to\n";
168+
out << " manual configure/build for that check profile.\n";
169+
out << " - In project mode, --san and --ubsan also trigger runtime validation.\n";
170+
out << " - In script mode, sanitizers validate both build and runtime.\n";
171+
out << " - Global packages installed by Vix are integrated into project checks.\n\n";
172+
173+
print_help_section_header(out, "Examples");
174+
out << " vix check\n";
175+
out << " vix check --tests\n";
176+
out << " vix check --run\n";
177+
out << " vix check --tests --run\n";
178+
out << " vix check --san\n";
179+
out << " vix check --san --tests\n";
180+
out << " vix check --san --tests --run-timeout 20\n";
181+
out << " vix check --dir ./examples/api\n";
182+
out << " vix check main.cpp\n";
183+
out << " vix check main.cpp --san\n";
184+
out << " vix check main.cpp --ubsan\n";
143185

144186
return 0;
145187
}

0 commit comments

Comments
 (0)