Skip to content

Commit 90ce5e2

Browse files
committed
feat(cli): integrate registry deps with vix.app
1 parent 2e45e5f commit 90ce5e2

6 files changed

Lines changed: 361 additions & 28 deletions

File tree

include/vix/cli/app/AppManifest.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,21 @@ namespace vix::cli::app
186186
*/
187187
std::vector<std::string> modules;
188188

189+
/**
190+
* @brief External dependencies from the Vix Registry.
191+
*
192+
* Each entry uses the same package syntax as `vix add`:
193+
*
194+
* - "namespace/name"
195+
* - "namespace/name@version"
196+
* - "@namespace/name"
197+
* - "@namespace/name@version"
198+
*
199+
* Registry dependencies are resolved through vix.json/vix.lock
200+
* and linked through the generated Vix dependency CMake file.
201+
*/
202+
std::vector<std::string> deps;
203+
189204
/**
190205
* @brief CMake targets or libraries to link.
191206
*/

src/app/AppCMakeGenerator.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,63 @@ namespace vix::cli::app
115115
return name;
116116
}
117117

118+
static bool parse_registry_dep_alias(
119+
const std::string &raw,
120+
std::string &alias)
121+
{
122+
std::string value = raw;
123+
124+
value.erase(
125+
value.begin(),
126+
std::find_if(
127+
value.begin(),
128+
value.end(),
129+
[](unsigned char c)
130+
{
131+
return std::isspace(c) == 0;
132+
}));
133+
134+
value.erase(
135+
std::find_if(
136+
value.rbegin(),
137+
value.rend(),
138+
[](unsigned char c)
139+
{
140+
return std::isspace(c) == 0;
141+
})
142+
.base(),
143+
value.end());
144+
145+
if (value.empty())
146+
return false;
147+
148+
if (value.front() == '@')
149+
value.erase(value.begin());
150+
151+
const std::size_t slash = value.find('/');
152+
153+
if (slash == std::string::npos ||
154+
slash == 0 ||
155+
slash + 1 >= value.size())
156+
{
157+
return false;
158+
}
159+
160+
const std::size_t atVersion = value.find('@', slash + 1);
161+
162+
const std::string ns = value.substr(0, slash);
163+
const std::string name =
164+
atVersion == std::string::npos
165+
? value.substr(slash + 1)
166+
: value.substr(slash + 1, atVersion - slash - 1);
167+
168+
if (ns.empty() || name.empty())
169+
return false;
170+
171+
alias = ns + "::" + name;
172+
return true;
173+
}
174+
118175
// ---------------------------------------------------------------
119176
// Standard normalization
120177
// ---------------------------------------------------------------
@@ -328,6 +385,25 @@ namespace vix::cli::app
328385
out << "endif()\n\n";
329386
}
330387

388+
static void emit_registry_deps_loader(
389+
std::ostringstream &out,
390+
const AppManifest &manifest,
391+
const fs::path &projectDir)
392+
{
393+
if (manifest.deps.empty())
394+
return;
395+
396+
const fs::path depsFile =
397+
fs::absolute(projectDir / ".vix" / "vix_deps.cmake").lexically_normal();
398+
399+
out << "# Registry dependencies declared in vix.app\n";
400+
out << "if(EXISTS " << cmake_quoted_path(depsFile) << ")\n";
401+
out << " include(" << cmake_quoted_path(depsFile) << ")\n";
402+
out << "else()\n";
403+
out << " message(FATAL_ERROR \"vix.app dependencies are declared, but .vix/vix_deps.cmake was not found. Run: vix install\")\n";
404+
out << "endif()\n\n";
405+
}
406+
331407
static bool is_vix_package(const AppPackage &pkg)
332408
{
333409
const std::string name = lower_copy(pkg.name);
@@ -509,6 +585,47 @@ namespace vix::cli::app
509585
out << "\n";
510586
}
511587

588+
static void emit_registry_deps_links(
589+
std::ostringstream &out,
590+
const AppManifest &manifest,
591+
const std::string &targetName)
592+
{
593+
if (manifest.deps.empty())
594+
return;
595+
596+
out << "# Registry dependencies declared in vix.app\n";
597+
598+
for (const std::string &dep : manifest.deps)
599+
{
600+
std::string alias;
601+
602+
if (!parse_registry_dep_alias(dep, alias))
603+
{
604+
out << "message(FATAL_ERROR \"Invalid vix.app dependency: "
605+
<< dep
606+
<< "\")\n";
607+
continue;
608+
}
609+
610+
out << "if(TARGET " << alias << ")\n";
611+
out << " target_link_libraries("
612+
<< targetName
613+
<< " PRIVATE "
614+
<< alias
615+
<< ")\n";
616+
out << "else()\n";
617+
out << " message(FATAL_ERROR \"VIX_APP_DEP_TARGET_NOT_FOUND "
618+
<< "dependency="
619+
<< dep
620+
<< " target="
621+
<< alias
622+
<< "\")\n";
623+
out << "endif()\n";
624+
}
625+
626+
out << "\n";
627+
}
628+
512629
static void emit_output_dir(
513630
std::ostringstream &out,
514631
const AppManifest &manifest,
@@ -724,6 +841,10 @@ namespace vix::cli::app
724841
// targets are available to target_link_libraries.
725842
emit_packages(out, manifest.packages);
726843

844+
// Registry dependencies are generated by `vix deps` into
845+
// .vix/vix_deps.cmake and must be loaded before target linking.
846+
emit_registry_deps_loader(out, manifest, projectDir);
847+
727848
// User application modules must be loaded before linking their
728849
// alias targets into the generated application target.
729850
emit_modules_loader(out, projectDir);
@@ -732,6 +853,7 @@ namespace vix::cli::app
732853
emit_includes_and_defines(out, manifest, targetName, projectDir);
733854
emit_options_and_features(out, manifest, targetName);
734855
emit_links(out, manifest, targetName);
856+
emit_registry_deps_links(out, manifest, targetName);
735857
emit_modules_links(out, manifest, targetName);
736858
emit_output_dir(out, manifest, targetName);
737859
emit_resources(out, manifest, targetName, projectDir);

src/app/AppManifest.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,13 @@ namespace vix::cli::app
588588
return true;
589589
}
590590

591+
if (normalizedKey == "deps" ||
592+
normalizedKey == "dependencies")
593+
{
594+
manifest.deps = values;
595+
return true;
596+
}
597+
591598
if (normalizedKey == "compile_options" ||
592599
normalizedKey == "compileoptions" ||
593600
normalizedKey == "cxxflags")

src/app/AppProjectResolver.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919
#include <vix/cli/app/AppCMakeGenerator.hpp>
2020
#include <vix/cli/app/AppManifest.hpp>
2121

22+
#include <vix/cli/util/Lockfile.hpp>
23+
#include <vix/cli/util/Manifest.hpp>
24+
#include <vix/cli/util/Resolver.hpp>
25+
2226
#include <system_error>
27+
#include <fstream>
2328

2429
namespace vix::cli::app
2530
{
@@ -43,6 +48,124 @@ namespace vix::cli::app
4348
return out.lexically_normal();
4449
}
4550

51+
static fs::path app_manifest_json_path(const fs::path &projectDir)
52+
{
53+
return projectDir / "vix.json";
54+
}
55+
56+
static fs::path app_lock_path(const fs::path &projectDir)
57+
{
58+
return projectDir / "vix.lock";
59+
}
60+
61+
static bool parse_registry_dep_spec(
62+
const std::string &raw,
63+
std::string &packageId,
64+
std::string &version)
65+
{
66+
std::string value = raw;
67+
68+
while (!value.empty() &&
69+
std::isspace(static_cast<unsigned char>(value.front())) != 0)
70+
{
71+
value.erase(value.begin());
72+
}
73+
74+
while (!value.empty() &&
75+
std::isspace(static_cast<unsigned char>(value.back())) != 0)
76+
{
77+
value.pop_back();
78+
}
79+
80+
if (value.empty())
81+
return false;
82+
83+
if (value.front() == '@')
84+
value.erase(value.begin());
85+
86+
const std::size_t slash = value.find('/');
87+
88+
if (slash == std::string::npos || slash == 0 || slash + 1 >= value.size())
89+
return false;
90+
91+
const std::size_t atVersion = value.find('@', slash + 1);
92+
93+
if (atVersion == std::string::npos)
94+
{
95+
packageId = value;
96+
version.clear();
97+
return true;
98+
}
99+
100+
packageId = value.substr(0, atVersion);
101+
version = value.substr(atVersion + 1);
102+
103+
return !packageId.empty() && !version.empty();
104+
}
105+
106+
static bool sync_vix_app_registry_deps(
107+
const AppManifest &manifest,
108+
const fs::path &projectDir,
109+
std::string &error)
110+
{
111+
if (manifest.deps.empty())
112+
return true;
113+
114+
const fs::path manifestPath = app_manifest_json_path(projectDir);
115+
const fs::path lockPath = app_lock_path(projectDir);
116+
117+
for (const std::string &dep : manifest.deps)
118+
{
119+
std::string packageId;
120+
std::string version;
121+
122+
if (!parse_registry_dep_spec(dep, packageId, version))
123+
{
124+
error = "Invalid vix.app dependency: " + dep;
125+
return false;
126+
}
127+
128+
const std::string requested =
129+
version.empty() ? std::string("*") : version;
130+
131+
try
132+
{
133+
vix::cli::util::manifest::upsert_manifest_dependency_or_throw(
134+
manifestPath,
135+
vix::cli::util::manifest::Dependency{
136+
packageId,
137+
requested});
138+
}
139+
catch (const std::exception &ex)
140+
{
141+
error = std::string("Failed to update vix.json from vix.app deps: ") + ex.what();
142+
return false;
143+
}
144+
}
145+
146+
try
147+
{
148+
const auto manifestDependencies =
149+
vix::cli::util::manifest::read_manifest_dependencies_or_throw(
150+
manifestPath);
151+
152+
const auto lockedDependencies =
153+
vix::cli::util::resolver::resolve_project_dependencies_or_throw(
154+
manifestDependencies);
155+
156+
vix::cli::util::lockfile::write_lockfile_replace_all_or_throw(
157+
lockPath,
158+
lockedDependencies);
159+
}
160+
catch (const std::exception &ex)
161+
{
162+
error = std::string("Failed to resolve vix.app dependencies: ") + ex.what();
163+
return false;
164+
}
165+
166+
return true;
167+
}
168+
46169
static fs::path search_project_root(const fs::path &base)
47170
{
48171
fs::path current = normalize_absolute(base);
@@ -103,6 +226,17 @@ namespace vix::cli::app
103226
return result;
104227
}
105228

229+
std::string depsError;
230+
231+
if (!sync_vix_app_registry_deps(
232+
loadResult.manifest,
233+
projectDir,
234+
depsError))
235+
{
236+
result.error = depsError;
237+
return result;
238+
}
239+
106240
const AppCMakeGenerateResult generateResult =
107241
generate_app_cmake_project(
108242
loadResult.manifest,

0 commit comments

Comments
 (0)