Skip to content

Commit 35fd29b

Browse files
committed
feat(build): support global packages in vix build (parity with vix run)
1 parent b3d8a10 commit 35fd29b

File tree

3 files changed

+278
-3
lines changed

3 files changed

+278
-3
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
*
3+
* @file GlobalPackages.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*/
13+
#ifndef VIX_CLI_CMAKE_GLOBALPACKAGES_HPP
14+
#define VIX_CLI_CMAKE_GLOBALPACKAGES_HPP
15+
16+
#include <filesystem>
17+
#include <string>
18+
#include <vector>
19+
20+
namespace vix::cli::build
21+
{
22+
namespace fs = std::filesystem;
23+
24+
struct GlobalPackage
25+
{
26+
std::string id;
27+
std::string pkgDir;
28+
std::string includeDir{"include"};
29+
std::string type{"header-only"};
30+
fs::path installedPath;
31+
};
32+
33+
std::vector<GlobalPackage> load_global_packages();
34+
35+
std::string make_global_packages_cmake(
36+
const std::vector<GlobalPackage> &packages);
37+
38+
} // namespace vix::cli::build
39+
40+
#endif

src/cmake/GlobalPackages.cpp

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/**
2+
*
3+
* @file GlobalPackages.cpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*/
13+
#include <vix/cli/cmake/GlobalPackages.hpp>
14+
15+
#include <algorithm>
16+
#include <cstdlib>
17+
#include <fstream>
18+
#include <optional>
19+
#include <sstream>
20+
#include <string>
21+
22+
#include <nlohmann/json.hpp>
23+
24+
namespace vix::cli::build
25+
{
26+
namespace
27+
{
28+
static std::optional<std::string> home_dir()
29+
{
30+
#ifdef _WIN32
31+
const char *home = std::getenv("USERPROFILE");
32+
#else
33+
const char *home = std::getenv("HOME");
34+
#endif
35+
if (!home || std::string(home).empty())
36+
return std::nullopt;
37+
38+
return std::string(home);
39+
}
40+
41+
static fs::path vix_root()
42+
{
43+
if (const auto home = home_dir(); home)
44+
return fs::path(*home) / ".vix";
45+
46+
return fs::path(".vix");
47+
}
48+
49+
static fs::path global_manifest_path()
50+
{
51+
return vix_root() / "global" / "installed.json";
52+
}
53+
54+
static std::string dep_id_to_dir(std::string depId)
55+
{
56+
depId.erase(std::remove(depId.begin(), depId.end(), '@'), depId.end());
57+
std::replace(depId.begin(), depId.end(), '/', '.');
58+
return depId;
59+
}
60+
61+
static std::string cmake_quote(const std::string &value)
62+
{
63+
std::string out;
64+
out.reserve(value.size() + 8);
65+
out.push_back('"');
66+
67+
for (char c : value)
68+
{
69+
if (c == '\\')
70+
out += "\\\\";
71+
else if (c == '"')
72+
out += "\\\"";
73+
else
74+
out.push_back(c);
75+
}
76+
77+
out.push_back('"');
78+
return out;
79+
}
80+
81+
static bool is_header_only_package(const GlobalPackage &pkg)
82+
{
83+
return pkg.type == "header-only";
84+
}
85+
86+
static fs::path package_include_path(const GlobalPackage &pkg)
87+
{
88+
return pkg.installedPath / pkg.includeDir;
89+
}
90+
91+
static fs::path package_cmake_lists_path(const GlobalPackage &pkg)
92+
{
93+
return pkg.installedPath / "CMakeLists.txt";
94+
}
95+
96+
static std::string package_binary_dir_name(const GlobalPackage &pkg)
97+
{
98+
std::string name = pkg.pkgDir;
99+
if (name.empty())
100+
name = "pkg";
101+
102+
for (char &c : name)
103+
{
104+
if (c == '.' || c == '/' || c == '\\' || c == ':' || c == ' ')
105+
c = '_';
106+
}
107+
108+
return name;
109+
}
110+
} // namespace
111+
112+
std::vector<GlobalPackage> load_global_packages()
113+
{
114+
std::vector<GlobalPackage> out;
115+
116+
const fs::path manifestPath = global_manifest_path();
117+
if (!fs::exists(manifestPath))
118+
return out;
119+
120+
std::ifstream ifs(manifestPath);
121+
if (!ifs)
122+
return out;
123+
124+
nlohmann::json root;
125+
ifs >> root;
126+
127+
if (!root.is_object())
128+
return out;
129+
130+
if (!root.contains("packages") || !root["packages"].is_array())
131+
return out;
132+
133+
for (const auto &item : root["packages"])
134+
{
135+
if (!item.is_object())
136+
continue;
137+
138+
if (!item.contains("id") || !item["id"].is_string())
139+
continue;
140+
141+
if (!item.contains("installed_path") || !item["installed_path"].is_string())
142+
continue;
143+
144+
GlobalPackage pkg;
145+
pkg.id = item["id"].get<std::string>();
146+
pkg.pkgDir = dep_id_to_dir(pkg.id);
147+
pkg.installedPath = fs::path(item["installed_path"].get<std::string>());
148+
149+
if (item.contains("include") && item["include"].is_string())
150+
pkg.includeDir = item["include"].get<std::string>();
151+
152+
if (item.contains("type") && item["type"].is_string())
153+
pkg.type = item["type"].get<std::string>();
154+
155+
out.push_back(std::move(pkg));
156+
}
157+
158+
return out;
159+
}
160+
161+
std::string make_global_packages_cmake(const std::vector<GlobalPackage> &packages)
162+
{
163+
std::ostringstream out;
164+
165+
out << "# Auto-generated by Vix\n";
166+
out << "# Global packages integration for vix build\n\n";
167+
168+
if (packages.empty())
169+
{
170+
out << "# No global packages installed\n";
171+
return out.str();
172+
}
173+
174+
for (const auto &pkg : packages)
175+
{
176+
if (pkg.installedPath.empty())
177+
continue;
178+
179+
const fs::path includePath = package_include_path(pkg);
180+
const fs::path cmakeLists = package_cmake_lists_path(pkg);
181+
182+
out << "# Package: " << pkg.id << "\n";
183+
184+
if (!pkg.includeDir.empty())
185+
{
186+
out << "if (EXISTS " << cmake_quote(includePath.string()) << ")\n";
187+
out << " include_directories(" << cmake_quote(includePath.string()) << ")\n";
188+
out << "endif()\n";
189+
}
190+
191+
if (!is_header_only_package(pkg) && fs::exists(cmakeLists))
192+
{
193+
const std::string binDir =
194+
"${CMAKE_BINARY_DIR}/_vix_global/" + package_binary_dir_name(pkg);
195+
196+
out << "if (EXISTS " << cmake_quote(cmakeLists.string()) << ")\n";
197+
out << " add_subdirectory(\n";
198+
out << " " << cmake_quote(pkg.installedPath.string()) << "\n";
199+
out << " " << cmake_quote(binDir) << "\n";
200+
out << " EXCLUDE_FROM_ALL\n";
201+
out << " )\n";
202+
out << "endif()\n";
203+
}
204+
205+
out << "\n";
206+
}
207+
208+
return out.str();
209+
}
210+
211+
} // namespace vix::cli::build

src/commands/BuildCommand.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <vix/cli/util/Strings.hpp>
3737
#include <vix/cli/cmake/CMakeBuild.hpp>
3838
#include <vix/cli/cmake/Toolchain.hpp>
39+
#include <vix/cli/cmake/GlobalPackages.hpp>
3940

4041
namespace fs = std::filesystem;
4142
using namespace vix::cli::style;
@@ -468,7 +469,8 @@ namespace vix::commands::BuildCommand
468469
const process::Preset &p, const process::Options &opt,
469470
const fs::path &toolchainFile,
470471
const std::optional<std::string> &launcher,
471-
const std::optional<std::string> &fastLinkerFlag)
472+
const std::optional<std::string> &fastLinkerFlag,
473+
const fs::path &globalPackagesFile)
472474
{
473475
std::vector<std::pair<std::string, std::string>> vars;
474476
vars.reserve(32);
@@ -485,6 +487,9 @@ namespace vix::commands::BuildCommand
485487
if (!opt.targetTriple.empty())
486488
vars.emplace_back("VIX_TARGET_TRIPLE", opt.targetTriple);
487489

490+
if (!globalPackagesFile.empty())
491+
vars.emplace_back("CMAKE_PROJECT_TOP_LEVEL_INCLUDES", globalPackagesFile.string());
492+
488493
if (launcher && !launcher->empty())
489494
{
490495
vars.emplace_back("CMAKE_C_COMPILER_LAUNCHER", *launcher);
@@ -586,15 +591,21 @@ namespace vix::commands::BuildCommand
586591
plan.buildLog = plan.buildDir / "build.log";
587592
plan.sigFile = plan.buildDir / ".vix-config.sig";
588593
plan.toolchainFile = plan.buildDir / "vix-toolchain.cmake";
594+
const fs::path globalPackagesFile = plan.buildDir / "vix-global-packages.cmake";
589595

590596
std::string toolchainContent;
591597
if (!opt.targetTriple.empty())
592598
toolchainContent =
593599
build::toolchain_contents_for_triple(opt.targetTriple, opt.sysroot);
594600

601+
const auto globalPackages = build::load_global_packages();
602+
const std::string globalPackagesCMake =
603+
build::make_global_packages_cmake(globalPackages);
604+
595605
plan.cmakeVars = build_cmake_vars(
596606
plan.preset, opt, plan.toolchainFile,
597-
plan.launcher, plan.fastLinkerFlag);
607+
plan.launcher, plan.fastLinkerFlag,
608+
globalPackagesFile);
598609
plan.signature = make_signature(plan, opt, toolchainContent);
599610

600611
return plan;
@@ -631,6 +642,7 @@ namespace vix::commands::BuildCommand
631642
}
632643

633644
plan_ = *planOpt;
645+
const fs::path globalPackagesFile = plan_.buildDir / "vix-global-packages.cmake";
634646

635647
const bool defer = (!opt_.quiet && !opt_.cmakeVerbose);
636648
DeferredConsole out(defer);
@@ -671,7 +683,8 @@ namespace vix::commands::BuildCommand
671683

672684
plan_.cmakeVars = build_cmake_vars(
673685
plan_.preset, opt_, plan_.toolchainFile,
674-
plan_.launcher, plan_.fastLinkerFlag);
686+
plan_.launcher, plan_.fastLinkerFlag,
687+
globalPackagesFile);
675688

676689
if (!opt_.targetTriple.empty())
677690
tc = build::toolchain_contents_for_triple(opt_.targetTriple, opt_.sysroot);
@@ -704,6 +717,17 @@ namespace vix::commands::BuildCommand
704717
}
705718
}
706719

720+
const auto globalPackages = build::load_global_packages();
721+
const std::string globalPackagesCMake =
722+
build::make_global_packages_cmake(globalPackages);
723+
724+
if (!util::write_text_file_atomic(globalPackagesFile, globalPackagesCMake))
725+
{
726+
error("Failed to write global packages file: " + globalPackagesFile.string());
727+
hint("Check filesystem permissions.");
728+
return 1;
729+
}
730+
707731
if (!opt_.quiet)
708732
{
709733
out.print(" Using project directory:\n");

0 commit comments

Comments
 (0)