55 Version() : major_(0), minor_(0), patch_(0) {}
56 Version(uint64_t major, uint64_t minor, uint64_t patch)
57 : major_(major), minor_(minor), patch_(patch) {}
58 Version(uint64_t major, uint64_t minor, uint64_t patch,
59 const std::string& prerelease,
const std::string& build =
"")
60 : major_(major), minor_(minor), patch_(patch),
61 prerelease_(prerelease), build_(build) {}
63 uint64_t major()
const {
return major_; }
64 uint64_t minor()
const {
return minor_; }
65 uint64_t patch()
const {
return patch_; }
66 const std::string& prerelease()
const {
return prerelease_; }
67 const std::string& build()
const {
return build_; }
69 std::string str()
const {
70 std::string s = std::to_string(major_) +
"." +
71 std::to_string(minor_) +
"." +
72 std::to_string(patch_);
73 if (!prerelease_.empty()) s +=
"-" + prerelease_;
74 if (!build_.empty()) s +=
"+" + build_;
79 bool operator==(
const Version& o)
const {
80 return major_ == o.major_ && minor_ == o.minor_ &&
81 patch_ == o.patch_ && prerelease_ == o.prerelease_;
83 bool operator!=(
const Version& o)
const {
return !(*
this == o); }
84 bool operator<(
const Version& o)
const {
return compare(o) < 0; }
85 bool operator<=(
const Version& o)
const {
return compare(o) <= 0; }
86 bool operator>(
const Version& o)
const {
return compare(o) > 0; }
87 bool operator>=(
const Version& o)
const {
return compare(o) >= 0; }
90 uint64_t major_, minor_, patch_;
91 std::string prerelease_;
94 int compare(
const Version& o)
const {
96 if (major_ != o.major_)
return major_ < o.major_ ? -1 : 1;
97 if (minor_ != o.minor_)
return minor_ < o.minor_ ? -1 : 1;
98 if (patch_ != o.patch_)
return patch_ < o.patch_ ? -1 : 1;
102 if (prerelease_.empty() && !o.prerelease_.empty())
return 1;
103 if (!prerelease_.empty() && o.prerelease_.empty())
return -1;
104 if (prerelease_.empty() && o.prerelease_.empty())
return 0;
107 return compare_prerelease(prerelease_, o.prerelease_);
110 static std::vector<std::string> split_prerelease(
const std::string& s) {
111 std::vector<std::string> parts;
112 std::string::size_type start = 0, pos;
113 while ((pos = s.find(
'.', start)) != std::string::npos) {
114 parts.push_back(s.substr(start, pos - start));
117 parts.push_back(s.substr(start));
121 static bool is_numeric(
const std::string& s) {
122 if (s.empty())
return false;
123 for (
char c : s) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
127 static int compare_prerelease(
const std::string& a,
const std::string& b) {
128 auto pa = split_prerelease(a);
129 auto pb = split_prerelease(b);
131 for (
size_t i = 0; i < pa.size() && i < pb.size(); ++i) {
132 bool a_num = is_numeric(pa[i]);
133 bool b_num = is_numeric(pb[i]);
135 if (a_num && b_num) {
137 uint64_t na = std::stoull(pa[i]);
138 uint64_t nb = std::stoull(pb[i]);
139 if (na != nb)
return na < nb ? -1 : 1;
140 }
else if (a_num != b_num) {
142 return a_num ? -1 : 1;
145 int cmp = pa[i].compare(pb[i]);
146 if (cmp != 0)
return cmp < 0 ? -1 : 1;
151 if (pa.size() != pb.size())
return pa.size() < pb.size() ? -1 : 1;
161enum class Comparator {
177using ComparatorSet = std::vector<Constraint>;
184 std::vector<ComparatorSet> sets;
187 std::optional<Version> min_version()
const;
190 std::string selection_key()
const;
202inline std::optional<Version> parse_version(
const std::string& str);
217inline std::optional<VersionRange> parse_range(
const std::string& str);
220inline bool satisfies(
const Version& version,
const Constraint& constraint);
223inline bool satisfies(
const Version& version,
const ComparatorSet& set);
226inline bool satisfies(
const Version& version,
const VersionRange& range);
234inline std::optional<Version> select_best(
235 const std::vector<Version>& versions,
236 const VersionRange& range);
244inline std::string trim(
const std::string& in) {
246 while (start < in.size() && std::isspace(
static_cast<unsigned char>(in[start]))) ++start;
247 size_t end = in.size();
248 while (end > start && std::isspace(
static_cast<unsigned char>(in[end - 1]))) --end;
249 return in.substr(start, end - start);
252inline std::vector<std::string> split(
const std::string& s,
const std::string& delim) {
253 std::vector<std::string> parts;
256 while ((pos = s.find(delim, start)) != std::string::npos) {
257 parts.push_back(s.substr(start, pos - start));
258 start = pos + delim.length();
260 parts.push_back(s.substr(start));
264inline std::vector<std::string> tokenize(
const std::string& s) {
265 std::vector<std::string> tokens;
266 std::istringstream iss(s);
268 while (iss >> token) {
269 tokens.push_back(token);
275inline std::optional<Version> parse_version_impl(
const std::string& str) {
276 std::string s = trim(str);
277 if (s.empty())
return std::nullopt;
281 auto plus_pos = s.find(
'+');
282 if (plus_pos != std::string::npos) {
283 build = s.substr(plus_pos + 1);
284 s = s.substr(0, plus_pos);
288 std::string prerelease;
289 auto dash_pos = s.find(
'-');
290 if (dash_pos != std::string::npos) {
291 prerelease = s.substr(dash_pos + 1);
292 s = s.substr(0, dash_pos);
296 auto parts = split(s,
".");
297 if (parts.size() != 3)
return std::nullopt;
300 for (
const auto& p : parts) {
301 if (p.empty())
return std::nullopt;
303 if (!std::isdigit(
static_cast<unsigned char>(c)))
return std::nullopt;
306 uint64_t major = std::stoull(parts[0]);
307 uint64_t minor = std::stoull(parts[1]);
308 uint64_t patch = std::stoull(parts[2]);
309 return Version(major, minor, patch, prerelease, build);
316inline std::optional<ComparatorSet> expand_caret(
const std::string& version_str) {
317 auto v = parse_version_impl(version_str);
318 if (!v)
return std::nullopt;
321 set.push_back({Comparator::Ge, *v});
327 if (v->major() == 0) {
328 if (v->minor() == 0) {
331 set.push_back({Comparator::Eq, *v});
335 upper = Version(0, v->minor() + 1, 0);
337 upper = Version(v->major() + 1, 0, 0);
339 set.push_back({Comparator::Lt, upper});
344inline std::optional<ComparatorSet> expand_tilde(
const std::string& version_str) {
345 auto v = parse_version_impl(version_str);
346 if (!v)
return std::nullopt;
349 set.push_back({Comparator::Ge, *v});
350 set.push_back({Comparator::Lt, Version(v->major(), v->minor() + 1, 0)});
355inline std::optional<ComparatorSet> expand_x_range(
const std::string& str) {
356 std::string s = trim(str);
359 if (s ==
"*" || s ==
"x" || s ==
"X") {
360 return ComparatorSet{};
364 auto parts = split(s,
".");
365 if (parts.empty())
return std::nullopt;
368 if (parts.size() == 1 || (parts.size() >= 2 && (parts[1] ==
"x" || parts[1] ==
"X" || parts[1] ==
"*"))) {
370 uint64_t major = std::stoull(parts[0]);
372 set.push_back({Comparator::Ge, Version(major, 0, 0)});
373 set.push_back({Comparator::Lt, Version(major + 1, 0, 0)});
377 if (parts.size() >= 3 && (parts[2] ==
"x" || parts[2] ==
"X" || parts[2] ==
"*")) {
379 uint64_t major = std::stoull(parts[0]);
380 uint64_t minor = std::stoull(parts[1]);
382 set.push_back({Comparator::Ge, Version(major, minor, 0)});
383 set.push_back({Comparator::Lt, Version(major, minor + 1, 0)});
393inline std::optional<Constraint> parse_constraint(
const std::string& str) {
394 std::string s = trim(str);
395 if (s.empty())
return std::nullopt;
397 Comparator op = Comparator::Eq;
398 std::string version_str;
400 if (s.rfind(
">=", 0) == 0) {
402 version_str = s.substr(2);
403 }
else if (s.rfind(
"<=", 0) == 0) {
405 version_str = s.substr(2);
406 }
else if (s.rfind(
">", 0) == 0) {
408 version_str = s.substr(1);
409 }
else if (s.rfind(
"<", 0) == 0) {
411 version_str = s.substr(1);
412 }
else if (s.rfind(
"=", 0) == 0) {
414 version_str = s.substr(1);
420 version_str = trim(version_str);
421 if (version_str.empty())
return std::nullopt;
423 auto version = parse_version_impl(version_str);
424 if (!version)
return std::nullopt;
425 return Constraint{op, *version};
428inline std::optional<ComparatorSet> parse_comparator_set(
const std::string& str) {
429 std::string s = trim(str);
430 if (s.empty())
return ComparatorSet{};
434 return expand_caret(s.substr(1));
439 return expand_tilde(s.substr(1));
443 if (s.find(
'x') != std::string::npos || s.find(
'X') != std::string::npos ||
444 s.find(
'*') != std::string::npos || s ==
"*") {
445 auto expanded = expand_x_range(s);
446 if (expanded)
return expanded;
450 auto tokens = tokenize(s);
451 if (tokens.empty())
return ComparatorSet{};
454 for (
const auto& token : tokens) {
456 if (!token.empty() && token[0] ==
'^') {
457 auto expanded = expand_caret(token.substr(1));
458 if (!expanded)
return std::nullopt;
459 for (
const auto& c : *expanded) set.push_back(c);
460 }
else if (!token.empty() && token[0] ==
'~') {
461 auto expanded = expand_tilde(token.substr(1));
462 if (!expanded)
return std::nullopt;
463 for (
const auto& c : *expanded) set.push_back(c);
465 auto constraint = parse_constraint(token);
466 if (!constraint)
return std::nullopt;
467 set.push_back(*constraint);
479inline std::optional<Version> VersionRange::min_version()
const {
480 std::optional<Version> min;
482 for (
const auto& set : sets) {
483 for (
const auto& constraint : set) {
484 if (constraint.op == Comparator::Ge || constraint.op == Comparator::Eq ||
485 constraint.op == Comparator::Gt) {
486 if (!min || constraint.version < *min) {
487 min = constraint.version;
496inline std::string VersionRange::selection_key()
const {
497 auto min = min_version();
499 return std::to_string(min->major()) +
"." + std::to_string(min->minor());
506inline std::optional<Version> parse_version(
const std::string& str) {
507 return detail::parse_version_impl(str);
510inline std::optional<VersionRange> parse_range(
const std::string& str) {
511 std::string s = detail::trim(str);
512 if (s.empty())
return std::nullopt;
515 auto or_parts = detail::split(s,
"||");
518 for (
const auto& part : or_parts) {
519 auto set = detail::parse_comparator_set(detail::trim(part));
520 if (!set)
return std::nullopt;
521 range.sets.push_back(*set);
524 if (range.sets.empty())
return std::nullopt;
528inline bool satisfies(
const Version& version,
const Constraint& constraint) {
529 switch (constraint.op) {
531 return version == constraint.version;
533 return version < constraint.version;
535 return version <= constraint.version;
537 return version > constraint.version;
539 return version >= constraint.version;
544inline bool satisfies(
const Version& version,
const ComparatorSet& set) {
546 if (set.empty())
return true;
549 for (
const auto& constraint : set) {
550 if (!satisfies(version, constraint)) {
557inline bool satisfies(
const Version& version,
const VersionRange& range) {
559 if (range.sets.empty())
return false;
562 for (
const auto& set : range.sets) {
563 if (satisfies(version, set)) {
570inline std::optional<Version> select_best(
571 const std::vector<Version>& versions,
572 const VersionRange& range)
574 std::optional<Version> best;
576 for (
const auto& v : versions) {
577 if (satisfies(v, range)) {
578 if (!best || v > *best) {
610struct NakSelectionResult {
613 std::string nak_version;
614 std::string record_ref;
615 std::string selection_reason;
616 std::vector<std::string> candidates;
625template<
typename RuntimeMap>
626inline NakSelectionResult select_nak_from_inventory(
627 const RuntimeMap& runtimes,
628 const std::string& nak_id,
629 const std::string& version_req)
631 NakSelectionResult result;
632 result.nak_id = nak_id;
635 auto range = parse_range(version_req);
637 result.error =
"Invalid version requirement: " + version_req;
642 std::vector<std::pair<Version, std::string>> matches;
644 for (
const auto& [record_ref, runtime] : runtimes) {
646 if (runtime.nak.id != nak_id) {
651 auto version = parse_version(runtime.nak.version);
657 if (satisfies(*version, *range)) {
658 matches.push_back({*version, record_ref});
659 result.candidates.push_back(runtime.nak.version);
663 if (matches.empty()) {
664 result.error =
"No NAK found matching " + nak_id +
" " + version_req;
669 auto best = std::max_element(matches.begin(), matches.end(),
670 [](
const auto& a,
const auto& b) { return a.first < b.first; });
673 result.nak_version = best->first.str();
674 result.record_ref = best->second;
675 result.selection_reason =
"highest_matching_version";