Skip to content

Commit

Permalink
[vcpkg] Improve various versioning error messages (microsoft#7)
Browse files Browse the repository at this point in the history
* [vcpkg] Improve various versioning error messages

With suggested fixes and additional help links

* Fix tests to accomodate new error messages

* Resolve MSVC shadowed variable warning

Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
  • Loading branch information
ras0219 and ras0219-msft authored Feb 11, 2021
1 parent 29f09f8 commit ddead91
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 28 deletions.
2 changes: 2 additions & 0 deletions include/vcpkg/versions.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ namespace vcpkg::Versions
String
};

void to_string(std::string& out, Scheme scheme);

struct VersionSpec
{
std::string port_name;
Expand Down
44 changes: 42 additions & 2 deletions src/vcpkg-test/dependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,18 @@ TEST_CASE ("version install diamond date", "[versionplan]")
check_name_and_version(install_plan.install_actions[2], "a", {"2020-01-03", 0});
}

static void CHECK_LINES(const std::string& a, const std::string& b)
{
auto as = Strings::split(a, '\n');
auto bs = Strings::split(b, '\n');
for (size_t i = 0; i < as.size() && i < bs.size(); ++i)
{
INFO(i);
CHECK(as[i] == bs[i]);
}
CHECK(as.size() == bs.size());
}

TEST_CASE ("version install scheme failure", "[versionplan]")
{
MockVersionedPortfileProvider vp;
Expand All @@ -1008,7 +1020,21 @@ TEST_CASE ("version install scheme failure", "[versionplan]")
toplevel_spec());

REQUIRE(!install_plan.error().empty());
CHECK(install_plan.error() == "Version conflict on a@1.0.1: baseline required 1.0.0");
CHECK_LINES(
install_plan.error(),
R"(Error: Version conflict on a:x86-windows: baseline required 1.0.0 but vcpkg could not compare it to 1.0.1
The two versions used incomparable schemes:
"1.0.1" was of scheme relaxed
"1.0.0" was of scheme semver
This can be resolved by adding an explicit override to the preferred version, for example:
"overrides": [
{ "name": "a", "version": "1.0.1" }
]
See `vcpkg help versioning` for more information.)");
}
SECTION ("higher baseline")
{
Expand All @@ -1024,7 +1050,21 @@ TEST_CASE ("version install scheme failure", "[versionplan]")
toplevel_spec());

REQUIRE(!install_plan.error().empty());
CHECK(install_plan.error() == "Version conflict on a@1.0.1: baseline required 1.0.2");
CHECK_LINES(
install_plan.error(),
R"(Error: Version conflict on a:x86-windows: baseline required 1.0.2 but vcpkg could not compare it to 1.0.1
The two versions used incomparable schemes:
"1.0.1" was of scheme relaxed
"1.0.2" was of scheme semver
This can be resolved by adding an explicit override to the preferred version, for example:
"overrides": [
{ "name": "a", "version": "1.0.1" }
]
See `vcpkg help versioning` for more information.)");
}
}

Expand Down
102 changes: 76 additions & 26 deletions src/vcpkg/dependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1222,7 +1222,7 @@ namespace vcpkg::Dependencies

void add_roots(View<Dependency> dep, const PackageSpec& toplevel);

ExpectedS<ActionPlan> finalize_extract_plan();
ExpectedS<ActionPlan> finalize_extract_plan(const PackageSpec& toplevel);

private:
const IVersionedPortfileProvider& m_ver_provider;
Expand All @@ -1243,6 +1243,7 @@ namespace vcpkg::Dependencies
// "relaxed" versions all share the same column
struct VersionSchemeInfo
{
Versions::Scheme scheme;
const SourceControlFileLocation* scfl = nullptr;
Versions::Version version;
// This tracks a list of constraint sources for debugging purposes
Expand Down Expand Up @@ -1312,6 +1313,11 @@ namespace vcpkg::Dependencies

Optional<Versions::Version> dep_to_version(const std::string& name, const DependencyConstraint& dc);

static std::string format_incomparable_versions_message(const PackageSpec& on,
StringView from,
const VersionSchemeInfo& current,
const VersionSchemeInfo& target);

std::vector<std::string> m_errors;
};

Expand Down Expand Up @@ -1367,6 +1373,7 @@ namespace vcpkg::Dependencies
// not implemented
Checks::unreachable(VCPKG_LINE_INFO);
}
vsi->scheme = scheme;
vermap.emplace(ver, vsi);
return *vsi;
}
Expand Down Expand Up @@ -1402,8 +1409,8 @@ namespace vcpkg::Dependencies
{
Checks::check_exit(VCPKG_LINE_INFO, scfl);
ASSUME(scfl != nullptr);
auto scheme = scfl->source_control_file->core_paragraph->version_scheme;
auto r = compare_versions(scheme, version, scheme, new_ver);
auto s = scfl->source_control_file->core_paragraph->version_scheme;
auto r = compare_versions(s, version, s, new_ver);
Checks::check_exit(VCPKG_LINE_INFO, r != VerComp::unk);
return r == VerComp::lt;
}
Expand Down Expand Up @@ -1654,6 +1661,19 @@ namespace vcpkg::Dependencies
m_overrides.emplace(name, v);
}

static std::string format_missing_baseline_message(const std::string& onto, const PackageSpec& from)
{
return Strings::concat(
"Error: Cannot resolve a minimum constraint for dependency ",
onto,
" from ",
from,
".\nThe dependency was not found in the baseline, indicating that the package did not "
"exist at that time. This may be fixed by providing an explicit override version via the "
"\"overrides\" field or by updating the baseline.\nSee `vcpkg help versioning` for more "
"information.");
}

void VersionedPackageGraph::add_roots(View<Dependency> deps, const PackageSpec& toplevel)
{
auto dep_to_spec = [&toplevel, this](const Dependency& d) {
Expand Down Expand Up @@ -1750,9 +1770,7 @@ namespace vcpkg::Dependencies
}
else
{
m_errors.push_back(Strings::concat("Cannot resolve unversioned dependency from top-level to ",
dep.name,
" without a baseline entry or override."));
m_errors.push_back(format_missing_baseline_message(dep.name, toplevel));
}
}

Expand All @@ -1767,7 +1785,38 @@ namespace vcpkg::Dependencies
}
}

ExpectedS<ActionPlan> VersionedPackageGraph::finalize_extract_plan()
std::string VersionedPackageGraph::format_incomparable_versions_message(const PackageSpec& on,
StringView from,
const VersionSchemeInfo& current,
const VersionSchemeInfo& target)
{
return Strings::concat(
"Error: Version conflict on ",
on,
": ",
from,
" required ",
target.version,
" but vcpkg could not compare it to ",
current.version,
"\n\nThe two versions used incomparable schemes:\n \"",
current.version,
"\" was of scheme ",
current.scheme,
"\n \"",
target.version,
"\" was of scheme ",
target.scheme,
"\n\nThis can be resolved by adding an explicit override to the preferred version, for example:\n\n",
Strings::format(R"( "overrides": [
{ "name": "%s", "version": "%s" }
])",
on.name(),
current.version),
"\n\nSee `vcpkg help versioning` for more information.");
}

ExpectedS<ActionPlan> VersionedPackageGraph::finalize_extract_plan(const PackageSpec& toplevel)
{
if (m_errors.size() > 0)
{
Expand All @@ -1786,7 +1835,8 @@ namespace vcpkg::Dependencies
std::vector<Frame> stack;

auto push = [&emitted, this, &stack](const PackageSpec& spec,
const Versions::Version& new_ver) -> Optional<std::string> {
const Versions::Version& new_ver,
const PackageSpec& origin) -> Optional<std::string> {
auto&& node = m_graph[spec];
auto overlay = m_o_provider.get_control_file(spec.name());
auto over_it = m_overrides.find(spec.name());
Expand All @@ -1799,7 +1849,16 @@ namespace vcpkg::Dependencies
else
p_vnode = node.get_node(new_ver);

if (!p_vnode) return Strings::concat("Version was not found during discovery: ", spec, "@", new_ver);
if (!p_vnode)
{
return Strings::concat(
"Error: Version was not found during discovery: ",
spec,
"@",
new_ver,
"\nThis is an internal vcpkg error. Please open an issue on https://github.com/Microsoft/vcpkg "
"with detailed steps to reproduce the problem.");
}

auto p = emitted.emplace(spec, nullptr);
if (p.second)
Expand All @@ -1814,12 +1873,7 @@ namespace vcpkg::Dependencies
{
if (base_node != p_vnode)
{
return Strings::concat("Version conflict on ",
spec.name(),
"@",
new_ver,
": baseline required ",
*baseline.get());
return format_incomparable_versions_message(spec, "baseline", *p_vnode, *base_node);
}
}
}
Expand Down Expand Up @@ -1854,11 +1908,7 @@ namespace vcpkg::Dependencies
}
else
{
return Strings::concat("Cannot resolve unconstrained dependency from ",
spec.name(),
" to ",
dep.name,
" without a baseline entry or override.");
return format_missing_baseline_message(dep.name, spec);
}
}
}
Expand All @@ -1872,24 +1922,24 @@ namespace vcpkg::Dependencies
if (p.first->second == nullptr)
{
return Strings::concat(
"Cycle detected during ",
"Error: Cycle detected during ",
spec,
":\n",
Strings::join("\n", stack, [](const auto& p) -> const PackageSpec& { return p.ipa.spec; }));
}
else if (p.first->second != p_vnode)
{
// comparable versions should retrieve the same info node
return Strings::concat(
"Version conflict on ", spec.name(), "@", p.first->second->version, ": required ", new_ver);
return format_incomparable_versions_message(
spec, origin.to_string(), *p_vnode, *p.first->second);
}
return nullopt;
}
};

for (auto&& root : m_roots)
{
if (auto err = push(root.spec, root.ver))
if (auto err = push(root.spec, root.ver, toplevel))
{
return std::move(*err.get());
}
Expand All @@ -1908,7 +1958,7 @@ namespace vcpkg::Dependencies
{
auto dep = std::move(back.deps.back());
back.deps.pop_back();
if (auto err = push(dep.spec, dep.ver))
if (auto err = push(dep.spec, dep.ver, back.ipa.spec))
{
return std::move(*err.get());
}
Expand All @@ -1932,6 +1982,6 @@ namespace vcpkg::Dependencies
for (auto&& o : overrides)
vpg.add_override(o.name, {o.version, o.port_version});
vpg.add_roots(deps, toplevel);
return vpg.finalize_extract_plan();
return vpg.finalize_extract_plan(toplevel);
}
}
14 changes: 14 additions & 0 deletions src/vcpkg/help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ namespace vcpkg::Help
"be used to handle version conflicts, such as with `version-string` dependencies. They will not be "
"considered when transitively depended upon.");
tbl.blank();
tbl.text(
"Vcpkg will select the minimum version found that matches all applicable constraints, including the "
"version from the baseline specified at top-level as well as any \"version>=\" constraints in the graph.");
tbl.blank();
tbl.text("To keep your libraries up to date, the best approach is to update your baseline reference. This will "
"ensure all packages, including transitive ones, are updated. However if you need to update a package "
"independently, you can use a \"version>=\" constraint.");
tbl.blank();
tbl.text(
"Additionally, package publishers can use \"version>=\" constraints to ensure that consumers are using at "
"least a certain minimum version of a given dependency. For example, if a library needs an API added "
"to boost-asio in 1.70, a \"version>=\" constraint will ensure transitive users use a sufficient version "
"even in the face of individual version overrides or cross-registry references.");
tbl.blank();
tbl.text("Example manifest:");
tbl.blank();
tbl.text(R"({
Expand Down
24 changes: 24 additions & 0 deletions src/vcpkg/versions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ namespace vcpkg::Versions
return ret;
}

void to_string(std::string& out, Scheme scheme)
{
if (scheme == Scheme::String)
{
out.append("string");
}
else if (scheme == Scheme::Semver)
{
out.append("semver");
}
else if (scheme == Scheme::Relaxed)
{
out.append("relaxed");
}
else if (scheme == Scheme::Date)
{
out.append("date");
}
else
{
Checks::unreachable(VCPKG_LINE_INFO);
}
}

VerComp compare(const std::string& a, const std::string& b, Scheme scheme)
{
if (scheme == Scheme::String)
Expand Down

0 comments on commit ddead91

Please sign in to comment.