// file : bpkg/satisfaction.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2019 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #include #include #include #include using namespace std; using namespace butl; namespace bpkg { bool satisfies (const version& v, const dependency_constraint& c) { assert (!c.empty () && c.complete ()); if (v == wildcard_version) return true; bool s (true); // Here an absent revision means zero revision and version X must satisfy // the [X+0 ...) dependency constraint. Note that technically X < X+0. // version ev (v.epoch, v.upstream, v.release, v.effective_revision (), v.iteration); // See notes in pkg-build:query_available() on ignoring revision in // comparison. // if (c.min_version) { int i (ev.compare (*c.min_version, !c.min_version->revision)); s = c.min_open ? i > 0 : i >= 0; } if (s && c.max_version) { int i (ev.compare (*c.max_version, !c.max_version->revision)); s = c.max_open ? i < 0 : i <= 0; } return s; } bool satisfies (const dependency_constraint& l, const dependency_constraint& r) { assert (!l.empty () && l.complete () && !r.empty () && r.complete ()); // Note that a revision should not be ignored if we compare the endpoint // versions. However, an absent revision translates into the effective // revision differently, depending on the range endpoint side and openness // (see libbpkg/manifest.hxx for details). That's why we normalize // endpoint versions prior to comparison. // auto norm = [] (const version& v, bool min, bool open) -> version { return version (v.epoch, v.upstream, v.release, v.revision ? v.revision : (min && !open) || (!min && open) ? 0 : uint16_t (~0), v.iteration); }; bool s (false); if (l.min_version) { if (r.min_version) { version lv (norm (*l.min_version, true /* min */, l.min_open)); version rv (norm (*r.min_version, true /* min */, r.min_open)); int i (lv.compare (rv, false /* ignore_revision */)); if (l.min_open) // Doesn't matter if r is min_open or not. // s = i >= 0; else s = r.min_open ? i > 0 : i >= 0; } else s = true; // Doesn't matter what l.min_version is. } else s = !r.min_version; if (s) { if (l.max_version) { if (r.max_version) { version lv (norm (*l.max_version, false /* min */, l.max_open)); version rv (norm (*r.max_version, false /* min */, r.max_open)); int i (lv.compare (rv, false /* ignore_revision */)); if (l.max_open) // Doesn't matter if r is max_open or not. // s = i <= 0; else s = r.max_open ? i < 0 : i <= 0; } else { // Doesn't matter what l.max_version is, so leave s to be true. } } else s = !r.max_version; } return s; } static version build2_version; void satisfy_build2 (const common_options& o, const package_name& pkg, const dependency& d) { assert (d.name == "build2"); // Extract, parse, and cache build2 version string. // if (build2_version.empty ()) { fdpipe pipe (open_pipe ()); process pr (start_b (o, pipe, 2 /* stderr */, verb_b::quiet, "--version")); // Shouldn't throw, unless something is severely damaged. // pipe.out.close (); string l; try { ifdstream is (move (pipe.in), fdstream_mode::skip); getline (is, l); is.close (); if (pr.wait () && l.compare (0, 7, "build2 ") == 0) { try { build2_version = version (string (l, 7)); } catch (const invalid_argument&) {} // Fall through. } // Fall through. } catch (const io_error&) { pr.wait (); // Fall through. } if (build2_version.empty ()) fail << "unable to determine build2 version of " << name_b (o); } if (!satisfies (build2_version, d.constraint)) fail << "unable to satisfy constraint (" << d << ") for package " << pkg << info << "available build2 version is " << build2_version; } static version bpkg_version; void satisfy_bpkg (const common_options&, const package_name& pkg, const dependency& d) { assert (d.name == "bpkg"); // Parse and cache bpkg version string. // if (bpkg_version.empty ()) bpkg_version = version (BPKG_VERSION_STR); if (!satisfies (bpkg_version, d.constraint)) fail << "unable to satisfy constraint (" << d << ") for package " << pkg << info << "available bpkg version is " << bpkg_version; } }