[M5+] add resolver::conan_probe

This commit is contained in:
2026-05-10 10:14:38 +00:00
parent e63ac69239
commit e5c173b466
8 changed files with 352 additions and 2 deletions

View File

@@ -28,3 +28,5 @@ cargoxx_add_test(nixpkgs_probe_parse)
cargoxx_add_test(nixpkgs_probe_live)
cargoxx_add_test(nix_cmake_scan_parse)
cargoxx_add_test(nix_cmake_scan_live)
cargoxx_add_test(conan_probe_parse)
cargoxx_add_test(conan_probe_live)

View File

@@ -0,0 +1,41 @@
// Network-gated integration test for resolver::conan_probe.
#include <catch2/catch_test_macros.hpp>
import cargoxx.resolver;
import cargoxx.util;
import std;
namespace {
auto network_tests_enabled() -> bool {
auto* env = std::getenv("CARGOXX_NETWORK_TESTS");
return env != nullptr && std::string_view{env} == "1";
}
} // namespace
TEST_CASE("conan_probe finds 'fmt' in conan-center-index",
"[resolver][network]") {
if (!network_tests_enabled()) {
SKIP("CARGOXX_NETWORK_TESTS != 1");
}
auto r = cargoxx::resolver::conan_probe("fmt");
REQUIRE(r.has_value());
REQUIRE_FALSE(r->find_package.empty());
REQUIRE_FALSE(r->targets.empty());
// fmt's recipe should set cmake_target_name = "fmt::fmt".
REQUIRE(r->targets.front() == "fmt::fmt");
}
TEST_CASE("conan_probe returns ResolutionUnknownPackage for a 404",
"[resolver][network]") {
if (!network_tests_enabled()) {
SKIP("CARGOXX_NETWORK_TESTS != 1");
}
auto r = cargoxx::resolver::conan_probe(
"definitely_not_a_real_pkg_cargoxx_xyzzy");
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code ==
cargoxx::util::ErrorCode::ResolutionUnknownPackage);
}

View File

@@ -0,0 +1,81 @@
#include <catch2/catch_test_macros.hpp>
import cargoxx.resolver;
import cargoxx.util;
import std;
using cargoxx::resolver::parse_conanfile;
using cargoxx::util::ErrorCode;
TEST_CASE("parse_conanfile picks up modern set_property form", "[resolver][conan]") {
constexpr std::string_view text = R"PY(
from conan import ConanFile
class FmtConan(ConanFile):
name = "fmt"
def package_info(self):
self.cpp_info.set_property("cmake_file_name", "fmt")
self.cpp_info.set_property("cmake_target_name", "fmt::fmt")
)PY";
auto r = parse_conanfile(text, "fmt");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "fmt CONFIG REQUIRED");
REQUIRE(r->targets == std::vector<std::string>{"fmt::fmt"});
}
TEST_CASE("parse_conanfile picks up legacy names[] form", "[resolver][conan]") {
constexpr std::string_view text = R"PY(
class SpdlogConan(ConanFile):
def package_info(self):
self.cpp_info.names["cmake_find_package"] = "spdlog"
)PY";
auto r = parse_conanfile(text, "spdlog");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "spdlog CONFIG REQUIRED");
// Derived target heuristic: spdlog::spdlog
REQUIRE(r->targets == std::vector<std::string>{"spdlog::spdlog"});
}
TEST_CASE("parse_conanfile derives cmake_file_name from a colon-namespaced target",
"[resolver][conan]") {
constexpr std::string_view text = R"PY(
self.cpp_info.set_property("cmake_target_name", "Boost::filesystem")
)PY";
auto r = parse_conanfile(text, "boost");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "Boost CONFIG REQUIRED");
REQUIRE(r->targets == std::vector<std::string>{"Boost::filesystem"});
}
TEST_CASE("parse_conanfile falls back to the package name when nothing matches",
"[resolver][conan]") {
constexpr std::string_view text = R"PY(
# This recipe gives us nothing useful at the cpp_info level.
class OpaqueConan(ConanFile):
name = "opaque"
)PY";
auto r = parse_conanfile(text, "opaque");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "opaque CONFIG REQUIRED");
REQUIRE(r->targets == std::vector<std::string>{"opaque::opaque"});
}
TEST_CASE("parse_conanfile errors when no information AND no fallback",
"[resolver][conan]") {
auto r = parse_conanfile("# nothing here", "");
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
}
TEST_CASE("parse_conanfile accepts single-quoted strings", "[resolver][conan]") {
constexpr std::string_view text = R"PY(
self.cpp_info.set_property('cmake_target_name', 'absl::strings')
self.cpp_info.set_property('cmake_file_name', 'absl')
)PY";
auto r = parse_conanfile(text, "abseil");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "absl CONFIG REQUIRED");
REQUIRE(r->targets == std::vector<std::string>{"absl::strings"});
}