Skip to content

Commit 35c1966

Browse files
committed
fix(cmake): support deps fallback and stabilize dependency ordering
2 parents ff12ed0 + 71c6e85 commit 35c1966

2 files changed

Lines changed: 511 additions & 176 deletions

File tree

src/commands/InstallCommand.cpp

Lines changed: 221 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,8 @@ namespace vix::commands
474474

475475
dep.dependencies.clear();
476476

477-
if (j.contains("dependencies"))
477+
auto read_dep_block = [&](const json &d)
478478
{
479-
const auto &d = j["dependencies"];
480-
481479
if (d.is_array())
482480
{
483481
for (const auto &item : d)
@@ -501,7 +499,13 @@ namespace vix::commands
501499
dep.dependencies.push_back(it.key());
502500
}
503501
}
504-
}
502+
};
503+
504+
if (j.contains("dependencies"))
505+
read_dep_block(j["dependencies"]);
506+
507+
if (dep.dependencies.empty() && j.contains("deps"))
508+
read_dep_block(j["deps"]);
505509
}
506510

507511
static std::vector<DepResolved> sort_deps_topologically(const std::vector<DepResolved> &deps)
@@ -719,52 +723,192 @@ namespace vix::commands
719723

720724
out << "set(_VIX_DEPS_DIR " << cmake_quote(project_deps_dir().string()) << ")\n\n";
721725

726+
out << "# ------------------------------------------------------\n";
727+
out << "# Internal helpers generated by Vix\n";
728+
out << "# ------------------------------------------------------\n\n";
729+
730+
out << "function(_vix_disable_dep_extras dep_ns dep_name)\n";
731+
out << " string(TOUPPER \"${dep_ns}\" _VIX_NS_UPPER)\n";
732+
out << " string(TOUPPER \"${dep_name}\" _VIX_NAME_UPPER)\n";
733+
out << "\n";
734+
out << " # Generic knobs used by many projects\n";
735+
out << " set(BUILD_TESTING OFF CACHE BOOL \"\" FORCE)\n";
736+
out << " set(BUILD_TESTS OFF CACHE BOOL \"\" FORCE)\n";
737+
out << " set(ENABLE_TESTS OFF CACHE BOOL \"\" FORCE)\n";
738+
out << " set(TESTS OFF CACHE BOOL \"\" FORCE)\n";
739+
out << " set(UNIT_TESTS OFF CACHE BOOL \"\" FORCE)\n";
740+
out << " set(BUILD_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
741+
out << " set(ENABLE_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
742+
out << " set(EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
743+
out << " set(BUILD_BENCHMARKS OFF CACHE BOOL \"\" FORCE)\n";
744+
out << " set(BENCHMARKS OFF CACHE BOOL \"\" FORCE)\n";
745+
out << " set(BUILD_DOCS OFF CACHE BOOL \"\" FORCE)\n";
746+
out << " set(ENABLE_DOCS OFF CACHE BOOL \"\" FORCE)\n";
747+
out << " set(DOCS OFF CACHE BOOL \"\" FORCE)\n";
748+
out << "\n";
749+
out << " # Namespace-specific knobs, e.g. CNERIUM_BUILD_TESTS\n";
750+
out << " set(${_VIX_NS_UPPER}_BUILD_TESTING OFF CACHE BOOL \"\" FORCE)\n";
751+
out << " set(${_VIX_NS_UPPER}_BUILD_TESTS OFF CACHE BOOL \"\" FORCE)\n";
752+
out << " set(${_VIX_NS_UPPER}_ENABLE_TESTS OFF CACHE BOOL \"\" FORCE)\n";
753+
out << " set(${_VIX_NS_UPPER}_TESTS OFF CACHE BOOL \"\" FORCE)\n";
754+
out << " set(${_VIX_NS_UPPER}_UNIT_TESTS OFF CACHE BOOL \"\" FORCE)\n";
755+
out << " set(${_VIX_NS_UPPER}_BUILD_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
756+
out << " set(${_VIX_NS_UPPER}_ENABLE_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
757+
out << " set(${_VIX_NS_UPPER}_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
758+
out << " set(${_VIX_NS_UPPER}_BUILD_BENCHMARKS OFF CACHE BOOL \"\" FORCE)\n";
759+
out << " set(${_VIX_NS_UPPER}_BENCHMARKS OFF CACHE BOOL \"\" FORCE)\n";
760+
out << " set(${_VIX_NS_UPPER}_BUILD_DOCS OFF CACHE BOOL \"\" FORCE)\n";
761+
out << " set(${_VIX_NS_UPPER}_ENABLE_DOCS OFF CACHE BOOL \"\" FORCE)\n";
762+
out << " set(${_VIX_NS_UPPER}_DOCS OFF CACHE BOOL \"\" FORCE)\n";
763+
out << "\n";
764+
out << " # Package-specific knobs, e.g. CNERIUM_HTTP_BUILD_TESTS\n";
765+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_BUILD_TESTING OFF CACHE BOOL \"\" FORCE)\n";
766+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_BUILD_TESTS OFF CACHE BOOL \"\" FORCE)\n";
767+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_ENABLE_TESTS OFF CACHE BOOL \"\" FORCE)\n";
768+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_TESTS OFF CACHE BOOL \"\" FORCE)\n";
769+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_UNIT_TESTS OFF CACHE BOOL \"\" FORCE)\n";
770+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_BUILD_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
771+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_ENABLE_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
772+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n";
773+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_BUILD_BENCHMARKS OFF CACHE BOOL \"\" FORCE)\n";
774+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_BENCHMARKS OFF CACHE BOOL \"\" FORCE)\n";
775+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_BUILD_DOCS OFF CACHE BOOL \"\" FORCE)\n";
776+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_ENABLE_DOCS OFF CACHE BOOL \"\" FORCE)\n";
777+
out << " set(${_VIX_NS_UPPER}_${_VIX_NAME_UPPER}_DOCS OFF CACHE BOOL \"\" FORCE)\n";
778+
out << "endfunction()\n\n";
779+
780+
out << "function(_vix_bridge_alias canonical actual)\n";
781+
out << " if(TARGET ${canonical})\n";
782+
out << " return()\n";
783+
out << " endif()\n";
784+
out << " if(NOT TARGET ${actual})\n";
785+
out << " return()\n";
786+
out << " endif()\n";
787+
out << "\n";
788+
out << " string(REPLACE \"::\" \"__\" _VIX_BRIDGE_SAFE ${canonical})\n";
789+
out << " set(_VIX_BRIDGE_TARGET \"vix_bridge__${_VIX_BRIDGE_SAFE}\")\n";
790+
out << "\n";
791+
out << " if(NOT TARGET ${_VIX_BRIDGE_TARGET})\n";
792+
out << " add_library(${_VIX_BRIDGE_TARGET} INTERFACE)\n";
793+
out << " target_link_libraries(${_VIX_BRIDGE_TARGET} INTERFACE ${actual})\n";
794+
out << " endif()\n";
795+
out << "\n";
796+
out << " if(NOT TARGET ${canonical})\n";
797+
out << " add_library(${canonical} ALIAS ${_VIX_BRIDGE_TARGET})\n";
798+
out << " endif()\n";
799+
out << "endfunction()\n\n";
800+
801+
out << "function(_vix_try_bridge_for_dep dep_ns dep_name)\n";
802+
out << " set(_VIX_CANONICAL \"${dep_ns}::${dep_name}\")\n";
803+
out << " if(TARGET ${_VIX_CANONICAL})\n";
804+
out << " return()\n";
805+
out << " endif()\n";
806+
out << "\n";
807+
out << " set(_VIX_CANDIDATES\n";
808+
out << " \"${dep_name}\"\n";
809+
out << " \"${dep_name}::${dep_name}\"\n";
810+
out << " \"${dep_ns}_${dep_name}\"\n";
811+
out << " \"${dep_ns}-${dep_name}\"\n";
812+
out << " \"${dep_ns}.${dep_name}\"\n";
813+
out << " )\n";
814+
out << "\n";
815+
out << " foreach(_VIX_CAND IN LISTS _VIX_CANDIDATES)\n";
816+
out << " if(TARGET ${_VIX_CAND})\n";
817+
out << " _vix_bridge_alias(${_VIX_CANONICAL} ${_VIX_CAND})\n";
818+
out << " return()\n";
819+
out << " endif()\n";
820+
out << " endforeach()\n";
821+
out << "endfunction()\n\n";
822+
722823
for (const auto &dep : deps)
723824
{
724825
const std::string safe = cmake_safe_target(dep.id);
725826
const std::string alias = cmake_alias_target(dep.id);
827+
const fs::path depSourceDir = dep.linkDir;
828+
const fs::path depCMake = depSourceDir / "CMakeLists.txt";
829+
const fs::path depIncludeDir = dep.linkDir / dep.include;
830+
const std::string buildDirName = "_vix_build_" + sanitize_id_dot(dep.id);
831+
832+
const auto slash = dep.id.find('/');
833+
const std::string depNs = (slash == std::string::npos) ? dep.id : dep.id.substr(0, slash);
834+
const std::string depName = (slash == std::string::npos) ? dep.id : dep.id.substr(slash + 1);
726835

727836
out << "# " << dep.id << " @" << dep.version << " (" << dep.commit << ")\n";
728837

729-
if (dep.type == "header-only" || dep.type == "header_only" || dep.type == "headers")
730-
{
731-
const fs::path inc = dep.linkDir / dep.include;
732-
out << "add_library(" << safe << " INTERFACE)\n";
733-
out << "add_library(" << alias << " ALIAS " << safe << ")\n";
734-
out << "target_include_directories(" << safe << " INTERFACE "
735-
<< cmake_quote(inc.string()) << ")\n";
736-
}
737-
else if (dep.type == "library" ||
738-
dep.type == "header-and-source" ||
739-
dep.type == "header_and_source" ||
740-
dep.type == "headers-and-sources")
741-
{
742-
const fs::path depSourceDir = dep.linkDir;
743-
const std::string buildDirName = "_vix_build_" + sanitize_id_dot(dep.id);
838+
const bool hasCMake = fs::exists(depCMake);
839+
const bool isHeaderOnly =
840+
dep.type == "header-only" ||
841+
dep.type == "header_only" ||
842+
dep.type == "headers";
744843

745-
out << "if(NOT TARGET " << alias << ")\n";
844+
const bool isCompiledLike =
845+
dep.type == "library" ||
846+
dep.type == "header-and-source" ||
847+
dep.type == "header_and_source" ||
848+
dep.type == "headers-and-sources";
849+
850+
if (hasCMake)
851+
{
852+
out << "_vix_disable_dep_extras(" << depNs << " " << depName << ")\n";
853+
out << "if(EXISTS " << cmake_quote(depCMake.string()) << ")\n";
746854
out << " add_subdirectory("
747855
<< cmake_quote(depSourceDir.string()) << " "
748-
<< cmake_quote((project_vix_dir() / buildDirName).string()) << ")\n";
856+
<< cmake_quote((project_vix_dir() / buildDirName).string())
857+
<< " EXCLUDE_FROM_ALL)\n";
749858
out << "endif()\n";
750-
859+
out << "_vix_try_bridge_for_dep(" << depNs << " " << depName << ")\n";
860+
}
861+
else if (isHeaderOnly)
862+
{
751863
out << "if(NOT TARGET " << alias << ")\n";
752-
out << " message(FATAL_ERROR "
753-
<< cmake_quote("Dependency " + dep.id + " did not define expected target " + alias)
754-
<< ")\n";
864+
out << " add_library(" << safe << " INTERFACE)\n";
865+
out << " add_library(" << alias << " ALIAS " << safe << ")\n";
866+
out << " target_include_directories(" << safe << " INTERFACE "
867+
<< cmake_quote(depIncludeDir.string()) << ")\n";
755868
out << "endif()\n";
756869
}
757-
else
870+
else if (isCompiledLike)
758871
{
759-
out << "message(WARNING "
760-
<< cmake_quote("Unsupported Vix package type for " + dep.id + ": " + dep.type)
872+
out << "message(FATAL_ERROR "
873+
<< cmake_quote(
874+
"Dependency " + dep.id +
875+
" is a compiled package but no CMakeLists.txt was found in " +
876+
depSourceDir.string())
761877
<< ")\n";
762878
}
879+
else
880+
{
881+
if (hasCMake)
882+
{
883+
out << "_vix_disable_dep_extras(" << depNs << " " << depName << ")\n";
884+
out << "if(EXISTS " << cmake_quote(depCMake.string()) << ")\n";
885+
out << " add_subdirectory("
886+
<< cmake_quote(depSourceDir.string()) << " "
887+
<< cmake_quote((project_vix_dir() / buildDirName).string())
888+
<< " EXCLUDE_FROM_ALL)\n";
889+
out << "endif()\n";
890+
out << "_vix_try_bridge_for_dep(" << depNs << " " << depName << ")\n";
891+
}
892+
else if (fs::exists(depIncludeDir))
893+
{
894+
out << "if(NOT TARGET " << alias << ")\n";
895+
out << " add_library(" << safe << " INTERFACE)\n";
896+
out << " add_library(" << alias << " ALIAS " << safe << ")\n";
897+
out << " target_include_directories(" << safe << " INTERFACE "
898+
<< cmake_quote(depIncludeDir.string()) << ")\n";
899+
out << "endif()\n";
900+
}
901+
else
902+
{
903+
out << "message(WARNING "
904+
<< cmake_quote("Unsupported Vix package type for " + dep.id + ": " + dep.type)
905+
<< ")\n";
906+
}
907+
}
763908

764909
out << "\n";
765910
}
766911
}
767-
768912
static void print_next_steps(const std::vector<DepResolved> &deps)
769913
{
770914
vix::cli::util::one_line_spacer(std::cout);
@@ -774,6 +918,38 @@ namespace vix::commands
774918
vix::cli::util::info(std::cout, "CMake integration generated");
775919
std::cout << "\n";
776920

921+
std::vector<std::string> aliases;
922+
aliases.reserve(deps.size());
923+
924+
for (const auto &d : deps)
925+
{
926+
const fs::path depCMake = d.linkDir / "CMakeLists.txt";
927+
const fs::path depIncludeDir = d.linkDir / d.include;
928+
929+
const bool hasCMake = fs::exists(depCMake);
930+
const bool hasIncludeDir = fs::exists(depIncludeDir);
931+
932+
const bool isHeaderOnly =
933+
d.type == "header-only" ||
934+
d.type == "header_only" ||
935+
d.type == "headers";
936+
937+
const bool isCompiledLike =
938+
d.type == "library" ||
939+
d.type == "header-and-source" ||
940+
d.type == "header_and_source" ||
941+
d.type == "headers-and-sources";
942+
943+
// We only suggest aliases that are expected to exist after include(.vix/vix_deps.cmake):
944+
// - header-only fallback packages created by Vix
945+
// - CMake-based packages that should expose their public alias/target
946+
if ((isHeaderOnly && hasIncludeDir) || hasCMake || isCompiledLike)
947+
aliases.push_back(cmake_alias_target(d.id));
948+
}
949+
950+
std::sort(aliases.begin(), aliases.end());
951+
aliases.erase(std::unique(aliases.begin(), aliases.end()), aliases.end());
952+
777953
vix::cli::util::warn_line(std::cout, "Next:");
778954
std::cout << " " << GRAY << "" << RESET
779955
<< "Add this to your "
@@ -782,12 +958,24 @@ namespace vix::commands
782958

783959
std::cout << " include(.vix/vix_deps.cmake)\n";
784960
std::cout << " add_executable(app main.cpp)\n";
785-
std::cout << " target_link_libraries(app PRIVATE";
786961

787-
for (const auto &d : deps)
788-
std::cout << " " << cmake_alias_target(d.id);
962+
if (!aliases.empty())
963+
{
964+
std::cout << " target_link_libraries(app PRIVATE";
965+
966+
for (const auto &alias : aliases)
967+
std::cout << " " << alias;
789968

790-
std::cout << ")\n\n";
969+
std::cout << ")\n";
970+
}
971+
972+
std::cout << "\n";
973+
974+
std::cout << " " << GRAY << "" << RESET
975+
<< "Link only the packages your target actually uses.\n";
976+
std::cout << " " << GRAY << "" << RESET
977+
<< "Packages with their own CMakeLists.txt are loaded automatically through "
978+
<< CYAN << ".vix/vix_deps.cmake" << RESET << ".\n\n";
791979
}
792980

793981
static int install_global_package(const std::string &specRaw)

0 commit comments

Comments
 (0)