mirror of
https://github.com/microsoft/vcpkg.git
synced 2024-12-27 18:31:15 +08:00
[vcpkg] Introduce create_versioned_install_plan()
(#14633)
* [vcpkg] Implement constraints in manifests * [vcpkg] Add SourceControlFile::check_against_feature_flags to prevent accidentally ignoring versioning fields * [vcpkg] Switch check_against_feature_flags to accept fs::path * [vcpkg] Implement overrides parsing in manifests * [vcpkg] Address CR comments * [vcpkg] Initial implementation of create_versioned_install_plan() * [vcpkg] Implement port-version minimums * [vcpkg] Implement relaxation phase * [vcpkg] Refactor tests to use check_name_and_version * [vcpkg] Implemented simple relaxed scheme * [vcpkg] More relaxed scheme tests * [vcpkg] Mixed scheme testing * [vcpkg] Support versions and features without defaults * [vcpkg] Support versions and features without defaults 2 * [vcpkg] Only consider greater of toplevel and baseilne * [vcpkg] Implement overrides * [vcpkg] Install defaults * [vcpkg] Handle defaults of transitive packages * [vcpkg] Fix warnings for Span of initializer_list * [vcpkg] Use CMakeVarProvider during versioned install * [vcpkg] Handle inter-feature dependencies * [vcpkg] Correctly handle qualified Dependencies at toplevel * [vcpkg] Address CR comments Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
This commit is contained in:
parent
6c9cda1635
commit
896498fdba
@ -6,19 +6,23 @@ namespace vcpkg::Test
|
||||
{
|
||||
struct MockCMakeVarProvider : CMakeVars::CMakeVarProvider
|
||||
{
|
||||
void load_generic_triplet_vars(Triplet triplet) const override { generic_triplet_vars[triplet] = {}; }
|
||||
using SMap = std::unordered_map<std::string, std::string>;
|
||||
void load_generic_triplet_vars(Triplet triplet) const override
|
||||
{
|
||||
generic_triplet_vars.emplace(triplet, SMap{});
|
||||
}
|
||||
|
||||
void load_dep_info_vars(Span<const PackageSpec> specs) const override
|
||||
{
|
||||
for (auto&& spec : specs)
|
||||
dep_info_vars[spec] = {};
|
||||
dep_info_vars.emplace(spec, SMap{});
|
||||
}
|
||||
|
||||
void load_tag_vars(Span<const FullPackageSpec> specs,
|
||||
const PortFileProvider::PortFileProvider& port_provider) const override
|
||||
{
|
||||
for (auto&& spec : specs)
|
||||
tag_vars[spec.package_spec] = {};
|
||||
tag_vars.emplace(spec.package_spec, SMap{});
|
||||
(void)(port_provider);
|
||||
}
|
||||
|
||||
|
@ -173,5 +173,15 @@ namespace vcpkg::Dependencies
|
||||
std::vector<std::string> features,
|
||||
CMakeVars::CMakeVarProvider& var_provider);
|
||||
|
||||
/// <param name="provider">Contains the ports of the current environment.</param>
|
||||
/// <param name="specs">Feature specifications to resolve dependencies for.</param>
|
||||
/// <param name="status_db">Status of installed packages in the current environment.</param>
|
||||
ExpectedS<ActionPlan> create_versioned_install_plan(const PortFileProvider::IVersionedPortfileProvider& vprovider,
|
||||
const PortFileProvider::IBaselineProvider& bprovider,
|
||||
const CMakeVars::CMakeVarProvider& var_provider,
|
||||
const std::vector<Dependency>& deps,
|
||||
const std::vector<DependencyOverride>& overrides,
|
||||
const PackageSpec& toplevel);
|
||||
|
||||
void print_plan(const ActionPlan& action_plan, const bool is_recursive = true, const fs::path& vcpkg_root_dir = {});
|
||||
}
|
||||
|
@ -4,4 +4,6 @@ namespace vcpkg::PortFileProvider
|
||||
{
|
||||
struct PortFileProvider;
|
||||
struct PathsPortFileProvider;
|
||||
struct IVersionedPortfileProvider;
|
||||
struct IBaselineProvider;
|
||||
}
|
||||
|
@ -163,9 +163,9 @@ namespace vcpkg
|
||||
struct DependencyOverride
|
||||
{
|
||||
std::string name;
|
||||
Versions::Scheme version_scheme = Versions::Scheme::String;
|
||||
std::string version;
|
||||
int port_version = 0;
|
||||
Versions::Scheme version_scheme = Versions::Scheme::String;
|
||||
|
||||
Json::Object extra_info;
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
namespace vcpkg::Versions
|
||||
{
|
||||
using Version = VersionT;
|
||||
|
||||
enum class Scheme
|
||||
{
|
||||
Relaxed,
|
||||
@ -16,11 +18,10 @@ namespace vcpkg::Versions
|
||||
{
|
||||
std::string port_name;
|
||||
VersionT version;
|
||||
Scheme scheme;
|
||||
|
||||
VersionSpec(const std::string& port_name, const VersionT& version, Scheme scheme);
|
||||
VersionSpec(const std::string& port_name, const VersionT& version);
|
||||
|
||||
VersionSpec(const std::string& port_name, const std::string& version_string, int port_version, Scheme scheme);
|
||||
VersionSpec(const std::string& port_name, const std::string& version_string, int port_version);
|
||||
|
||||
friend bool operator==(const VersionSpec& lhs, const VersionSpec& rhs);
|
||||
friend bool operator!=(const VersionSpec& lhs, const VersionSpec& rhs);
|
||||
|
@ -11,15 +11,19 @@ namespace vcpkg
|
||||
VersionT(const std::string& value, int port_version);
|
||||
|
||||
std::string to_string() const;
|
||||
void to_string(std::string& out) const;
|
||||
|
||||
friend bool operator==(const VersionT& left, const VersionT& right);
|
||||
friend bool operator!=(const VersionT& left, const VersionT& right);
|
||||
|
||||
friend struct VersionTMapLess;
|
||||
|
||||
const std::string text() const { return m_text; }
|
||||
int port_version() const { return m_port_version; }
|
||||
|
||||
private:
|
||||
std::string value;
|
||||
int port_version;
|
||||
std::string m_text;
|
||||
int m_port_version;
|
||||
};
|
||||
|
||||
struct VersionDiff
|
||||
|
File diff suppressed because it is too large
Load Diff
152
toolsrc/src/vcpkg-test/versionplan.cpp
Normal file
152
toolsrc/src/vcpkg-test/versionplan.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <vcpkg/dependencies.h>
|
||||
#include <vcpkg/paragraphparser.h>
|
||||
#include <vcpkg/portfileprovider.h>
|
||||
#include <vcpkg/sourceparagraph.h>
|
||||
|
||||
#include <vcpkg-test/mockcmakevarprovider.h>
|
||||
#include <vcpkg-test/util.h>
|
||||
|
||||
using namespace vcpkg;
|
||||
using namespace vcpkg::Parse;
|
||||
|
||||
TEST_CASE ("parse depends", "[dependencies]")
|
||||
{
|
||||
auto w = parse_dependencies_list("liba (windows)");
|
||||
REQUIRE(w);
|
||||
auto& v = *w.get();
|
||||
REQUIRE(v.size() == 1);
|
||||
REQUIRE(v.at(0).name == "liba");
|
||||
REQUIRE(v.at(0).platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}}));
|
||||
REQUIRE(v.at(0).platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}}));
|
||||
REQUIRE(!v.at(0).platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}}));
|
||||
}
|
||||
|
||||
TEST_CASE ("filter depends", "[dependencies]")
|
||||
{
|
||||
const std::unordered_map<std::string, std::string> x64_win_cmake_vars{{"VCPKG_TARGET_ARCHITECTURE", "x64"},
|
||||
{"VCPKG_CMAKE_SYSTEM_NAME", ""}};
|
||||
|
||||
const std::unordered_map<std::string, std::string> arm_uwp_cmake_vars{{"VCPKG_TARGET_ARCHITECTURE", "arm"},
|
||||
{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}};
|
||||
|
||||
auto deps_ = parse_dependencies_list("liba (!uwp), libb, libc (uwp)");
|
||||
REQUIRE(deps_);
|
||||
auto& deps = *deps_.get();
|
||||
auto v = filter_dependencies(deps, Test::X64_WINDOWS, x64_win_cmake_vars);
|
||||
REQUIRE(v.size() == 2);
|
||||
REQUIRE(v.at(0).package_spec.name() == "liba");
|
||||
REQUIRE(v.at(1).package_spec.name() == "libb");
|
||||
|
||||
auto v2 = filter_dependencies(deps, Test::ARM_UWP, arm_uwp_cmake_vars);
|
||||
REQUIRE(v.size() == 2);
|
||||
REQUIRE(v2.at(0).package_spec.name() == "libb");
|
||||
REQUIRE(v2.at(1).package_spec.name() == "libc");
|
||||
}
|
||||
|
||||
TEST_CASE ("parse feature depends", "[dependencies]")
|
||||
{
|
||||
auto u_ = parse_dependencies_list("libwebp[anim, gif2webp, img2webp, info, mux, nearlossless, "
|
||||
"simd, cwebp, dwebp], libwebp[vwebp-sdl, extras] (!osx)");
|
||||
REQUIRE(u_);
|
||||
auto& v = *u_.get();
|
||||
REQUIRE(v.size() == 2);
|
||||
auto&& a0 = v.at(0);
|
||||
REQUIRE(a0.name == "libwebp");
|
||||
REQUIRE(a0.features.size() == 9);
|
||||
REQUIRE(a0.platform.is_empty());
|
||||
|
||||
auto&& a1 = v.at(1);
|
||||
REQUIRE(a1.name == "libwebp");
|
||||
REQUIRE(a1.features.size() == 2);
|
||||
REQUIRE(!a1.platform.is_empty());
|
||||
REQUIRE(a1.platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}}));
|
||||
REQUIRE(a1.platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}}));
|
||||
REQUIRE_FALSE(a1.platform.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}}));
|
||||
}
|
||||
|
||||
TEST_CASE ("qualified dependency", "[dependencies]")
|
||||
{
|
||||
using namespace Test;
|
||||
PackageSpecMap spec_map;
|
||||
auto spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1] (linux)"), {}};
|
||||
auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {}};
|
||||
|
||||
PortFileProvider::MapPortFileProvider map_port{spec_map.map};
|
||||
MockCMakeVarProvider var_provider;
|
||||
|
||||
auto plan = vcpkg::Dependencies::create_feature_install_plan(map_port, var_provider, {spec_a}, {});
|
||||
REQUIRE(plan.install_actions.size() == 2);
|
||||
REQUIRE(plan.install_actions.at(0).feature_list == std::vector<std::string>{"core"});
|
||||
|
||||
FullPackageSpec linspec_a{{"a", Triplet::from_canonical_name("x64-linux")}, {}};
|
||||
var_provider.dep_info_vars[linspec_a.package_spec].emplace("VCPKG_CMAKE_SYSTEM_NAME", "Linux");
|
||||
auto plan2 = vcpkg::Dependencies::create_feature_install_plan(map_port, var_provider, {linspec_a}, {});
|
||||
REQUIRE(plan2.install_actions.size() == 2);
|
||||
REQUIRE(plan2.install_actions.at(0).feature_list == std::vector<std::string>{"b1", "core"});
|
||||
}
|
||||
|
||||
TEST_CASE ("resolve_deps_as_top_level", "[dependencies]")
|
||||
{
|
||||
using namespace Test;
|
||||
PackageSpecMap spec_map;
|
||||
FullPackageSpec spec_a{spec_map.emplace("a", "b, b[b1] (linux)"), {}};
|
||||
FullPackageSpec spec_b{spec_map.emplace("b", "", {{"b1", ""}}), {}};
|
||||
FullPackageSpec spec_c{spec_map.emplace("c", "b", {{"c1", "b[b1]"}, {"c2", "c[c1], a"}}, {"c1"}), {"core"}};
|
||||
FullPackageSpec spec_d{spec_map.emplace("d", "c[core]"), {}};
|
||||
|
||||
PortFileProvider::MapPortFileProvider map_port{spec_map.map};
|
||||
MockCMakeVarProvider var_provider;
|
||||
Triplet t_linux = Triplet::from_canonical_name("x64-linux");
|
||||
var_provider.dep_info_vars[{"a", t_linux}].emplace("VCPKG_CMAKE_SYSTEM_NAME", "Linux");
|
||||
{
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("a").source_control_file, Test::X86_WINDOWS, {}, var_provider);
|
||||
REQUIRE(deps.size() == 1);
|
||||
REQUIRE(deps.at(0) == spec_b);
|
||||
}
|
||||
{
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("a").source_control_file, t_linux, {}, var_provider);
|
||||
REQUIRE(deps.size() == 1);
|
||||
REQUIRE(deps.at(0) == FullPackageSpec({"b", t_linux}, {"b1"}));
|
||||
}
|
||||
{
|
||||
// without defaults
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("c").source_control_file, Test::X86_WINDOWS, {}, var_provider);
|
||||
REQUIRE(deps.size() == 1);
|
||||
REQUIRE(deps.at(0) == spec_b);
|
||||
}
|
||||
FullPackageSpec spec_b_with_b1{spec_b.package_spec, {"b1"}};
|
||||
{
|
||||
// with defaults of c (c1)
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("c").source_control_file, Test::X86_WINDOWS, {"default"}, var_provider);
|
||||
REQUIRE(deps.size() == 1);
|
||||
REQUIRE(deps.at(0) == spec_b_with_b1);
|
||||
}
|
||||
{
|
||||
// with c1
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("c").source_control_file, Test::X86_WINDOWS, {"c1"}, var_provider);
|
||||
REQUIRE(deps.size() == 1);
|
||||
REQUIRE(deps.at(0) == spec_b_with_b1);
|
||||
}
|
||||
{
|
||||
// with c2 implying c1
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("c").source_control_file, Test::X86_WINDOWS, {"c2"}, var_provider);
|
||||
REQUIRE(deps.size() == 2);
|
||||
REQUIRE(deps.at(0) == spec_a);
|
||||
REQUIRE(deps.at(1) == spec_b_with_b1);
|
||||
}
|
||||
{
|
||||
// d -> c[core]
|
||||
auto deps = vcpkg::Dependencies::resolve_deps_as_top_level(
|
||||
*spec_map.map.at("d").source_control_file, Test::X86_WINDOWS, {}, var_provider);
|
||||
REQUIRE(deps.size() == 1);
|
||||
REQUIRE(deps.at(0) == spec_c);
|
||||
}
|
||||
}
|
@ -628,7 +628,7 @@ namespace vcpkg::Dependencies
|
||||
auto ctx = [&]() -> const PlatformExpression::Context& {
|
||||
if (!ctx_storage)
|
||||
{
|
||||
var_provider.load_dep_info_vars({{spec}});
|
||||
var_provider.load_dep_info_vars({&spec, 1});
|
||||
ctx_storage = var_provider.get_dep_info_vars(spec);
|
||||
}
|
||||
return ctx_storage.value_or_exit(VCPKG_LINE_INFO);
|
||||
@ -1157,4 +1157,662 @@ namespace vcpkg::Dependencies
|
||||
Checks::exit_fail(VCPKG_LINE_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct VersionedPackageGraph
|
||||
{
|
||||
private:
|
||||
using IVersionedPortfileProvider = PortFileProvider::IVersionedPortfileProvider;
|
||||
using IBaselineProvider = PortFileProvider::IBaselineProvider;
|
||||
|
||||
public:
|
||||
VersionedPackageGraph(const IVersionedPortfileProvider& ver_provider,
|
||||
const IBaselineProvider& base_provider,
|
||||
const CMakeVars::CMakeVarProvider& var_provider)
|
||||
: m_ver_provider(ver_provider), m_base_provider(base_provider), m_var_provider(var_provider)
|
||||
{
|
||||
}
|
||||
|
||||
void add_override(const std::string& name, const Versions::Version& v);
|
||||
|
||||
void add_roots(View<Dependency> dep, const PackageSpec& toplevel);
|
||||
|
||||
ExpectedS<ActionPlan> finalize_extract_plan();
|
||||
|
||||
private:
|
||||
const IVersionedPortfileProvider& m_ver_provider;
|
||||
const IBaselineProvider& m_base_provider;
|
||||
const CMakeVars::CMakeVarProvider& m_var_provider;
|
||||
|
||||
struct DepSpec
|
||||
{
|
||||
PackageSpec spec;
|
||||
Versions::Version ver;
|
||||
std::vector<std::string> features;
|
||||
};
|
||||
|
||||
// This object contains the current version within a given column.
|
||||
// Each "string" scheme text is treated as a separate column
|
||||
// "relaxed" versions all share the same column
|
||||
struct VersionSchemeInfo
|
||||
{
|
||||
const SourceControlFileLocation* scfl = nullptr;
|
||||
Versions::Version version;
|
||||
// This tracks a list of constraint sources for debugging purposes
|
||||
std::vector<std::string> origins;
|
||||
std::map<std::string, std::vector<FeatureSpec>> deps;
|
||||
|
||||
bool is_less_than(const Versions::Version& new_ver) const;
|
||||
};
|
||||
|
||||
struct PackageNode : Util::MoveOnlyBase
|
||||
{
|
||||
std::map<Versions::Version, VersionSchemeInfo*, VersionTMapLess> vermap;
|
||||
std::map<std::string, VersionSchemeInfo> exacts;
|
||||
Optional<std::unique_ptr<VersionSchemeInfo>> relaxed;
|
||||
std::set<std::string> features;
|
||||
bool default_features = true;
|
||||
|
||||
VersionSchemeInfo* get_node(const Versions::Version& ver);
|
||||
VersionSchemeInfo& emplace_node(Versions::Scheme scheme, const Versions::Version& ver);
|
||||
};
|
||||
|
||||
std::vector<DepSpec> m_roots;
|
||||
std::map<std::string, Versions::Version> m_overrides;
|
||||
std::map<PackageSpec, PackageNode> m_graph;
|
||||
|
||||
std::pair<const PackageSpec, PackageNode>& emplace_package(const PackageSpec& spec);
|
||||
|
||||
void add_constraint(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const Dependency& dep,
|
||||
const std::string& origin);
|
||||
void add_constraint(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const Versions::Version& ver,
|
||||
const std::string& origin);
|
||||
void add_constraint(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const std::string& feature,
|
||||
const std::string& origin);
|
||||
|
||||
void add_constraint_default_features(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const std::string& origin);
|
||||
|
||||
void add_feature_to(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
VersionSchemeInfo& vsi,
|
||||
const std::string& feature);
|
||||
|
||||
std::vector<std::string> m_errors;
|
||||
};
|
||||
|
||||
VersionedPackageGraph::VersionSchemeInfo& VersionedPackageGraph::PackageNode::emplace_node(
|
||||
Versions::Scheme scheme, const Versions::Version& ver)
|
||||
{
|
||||
auto it = vermap.find(ver);
|
||||
if (it != vermap.end()) return *it->second;
|
||||
|
||||
VersionSchemeInfo* vsi = nullptr;
|
||||
if (scheme == Versions::Scheme::String)
|
||||
{
|
||||
vsi = &exacts[ver.text()];
|
||||
}
|
||||
else if (scheme == Versions::Scheme::Relaxed)
|
||||
{
|
||||
if (auto p = relaxed.get())
|
||||
{
|
||||
vsi = p->get();
|
||||
}
|
||||
else
|
||||
{
|
||||
relaxed = std::make_unique<VersionSchemeInfo>();
|
||||
vsi = relaxed.get()->get();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not implemented
|
||||
Checks::unreachable(VCPKG_LINE_INFO);
|
||||
}
|
||||
vermap.emplace(ver, vsi);
|
||||
return *vsi;
|
||||
}
|
||||
|
||||
VersionedPackageGraph::VersionSchemeInfo* VersionedPackageGraph::PackageNode::get_node(
|
||||
const Versions::Version& ver)
|
||||
{
|
||||
auto it = vermap.find(ver);
|
||||
return it == vermap.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
enum class VerComp
|
||||
{
|
||||
unk,
|
||||
lt,
|
||||
eq,
|
||||
gt,
|
||||
};
|
||||
static VerComp compare_versions(Versions::Scheme sa,
|
||||
const Versions::Version& a,
|
||||
Versions::Scheme sb,
|
||||
const Versions::Version& b)
|
||||
{
|
||||
if (sa != sb) return VerComp::unk;
|
||||
switch (sa)
|
||||
{
|
||||
case Versions::Scheme::String:
|
||||
{
|
||||
if (a.text() != b.text()) return VerComp::unk;
|
||||
if (a.port_version() < b.port_version()) return VerComp::lt;
|
||||
if (a.port_version() > b.port_version()) return VerComp::gt;
|
||||
return VerComp::eq;
|
||||
}
|
||||
case Versions::Scheme::Relaxed:
|
||||
{
|
||||
auto i1 = atoi(a.text().c_str());
|
||||
auto i2 = atoi(b.text().c_str());
|
||||
if (i1 < i2) return VerComp::lt;
|
||||
if (i1 > i2) return VerComp::gt;
|
||||
if (a.port_version() < b.port_version()) return VerComp::lt;
|
||||
if (a.port_version() > b.port_version()) return VerComp::gt;
|
||||
return VerComp::eq;
|
||||
}
|
||||
default: Checks::unreachable(VCPKG_LINE_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
bool VersionedPackageGraph::VersionSchemeInfo::is_less_than(const Versions::Version& new_ver) const
|
||||
{
|
||||
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);
|
||||
Checks::check_exit(VCPKG_LINE_INFO, r != VerComp::unk);
|
||||
return r == VerComp::lt;
|
||||
}
|
||||
|
||||
Versions::Version to_version(const SourceControlFile& scf)
|
||||
{
|
||||
return {
|
||||
scf.core_paragraph->version,
|
||||
scf.core_paragraph->port_version,
|
||||
};
|
||||
}
|
||||
Optional<Versions::Version> to_version(const DependencyConstraint& dc)
|
||||
{
|
||||
if (dc.type == Versions::Constraint::Type::None)
|
||||
{
|
||||
return nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Versions::Version{
|
||||
dc.value,
|
||||
dc.port_version,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void VersionedPackageGraph::add_feature_to(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
VersionSchemeInfo& vsi,
|
||||
const std::string& feature)
|
||||
{
|
||||
auto deps = vsi.scfl->source_control_file->find_dependencies_for_feature(feature);
|
||||
if (!deps)
|
||||
{
|
||||
// This version doesn't have this feature. This may result in an error during finalize if the
|
||||
// constraint is not removed via upgrades.
|
||||
return;
|
||||
}
|
||||
auto p = vsi.deps.emplace(feature, std::vector<FeatureSpec>{});
|
||||
if (!p.second)
|
||||
{
|
||||
// This feature has already been handled
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto&& dep : *deps.get())
|
||||
{
|
||||
PackageSpec dep_spec(dep.name, ref.first.triplet());
|
||||
|
||||
if (!dep.platform.is_empty())
|
||||
{
|
||||
auto maybe_vars = m_var_provider.get_dep_info_vars(ref.first);
|
||||
if (!maybe_vars)
|
||||
{
|
||||
m_var_provider.load_dep_info_vars({&ref.first, 1});
|
||||
maybe_vars = m_var_provider.get_dep_info_vars(ref.first);
|
||||
}
|
||||
|
||||
if (!dep.platform.evaluate(maybe_vars.value_or_exit(VCPKG_LINE_INFO)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto& dep_node = emplace_package(dep_spec);
|
||||
// Todo: cycle detection
|
||||
add_constraint(dep_node, dep, ref.first.name());
|
||||
|
||||
p.first->second.emplace_back(dep_spec, "core");
|
||||
for (auto&& f : dep.features)
|
||||
{
|
||||
p.first->second.emplace_back(dep_spec, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VersionedPackageGraph::add_constraint_default_features(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const std::string& origin)
|
||||
{
|
||||
(void)origin;
|
||||
if (!ref.second.default_features)
|
||||
{
|
||||
ref.second.default_features = true;
|
||||
|
||||
if (auto relaxed = ref.second.relaxed.get())
|
||||
{
|
||||
for (auto&& f : relaxed->get()->scfl->source_control_file->core_paragraph->default_features)
|
||||
{
|
||||
add_feature_to(ref, **relaxed, f);
|
||||
}
|
||||
}
|
||||
for (auto&& vsi : ref.second.exacts)
|
||||
{
|
||||
for (auto&& f : vsi.second.scfl->source_control_file->core_paragraph->default_features)
|
||||
{
|
||||
add_feature_to(ref, vsi.second, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VersionedPackageGraph::add_constraint(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const Dependency& dep,
|
||||
const std::string& origin)
|
||||
{
|
||||
auto base_ver = m_base_provider.get_baseline_version(dep.name);
|
||||
auto dep_ver = to_version(dep.constraint);
|
||||
|
||||
if (auto dv = dep_ver.get())
|
||||
{
|
||||
add_constraint(ref, *dv, origin);
|
||||
}
|
||||
|
||||
if (auto bv = base_ver.get())
|
||||
{
|
||||
add_constraint(ref, *bv, origin);
|
||||
}
|
||||
|
||||
for (auto&& f : dep.features)
|
||||
{
|
||||
add_constraint(ref, f, origin);
|
||||
}
|
||||
}
|
||||
void VersionedPackageGraph::add_constraint(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const Versions::Version& version,
|
||||
const std::string& origin)
|
||||
{
|
||||
auto over_it = m_overrides.find(ref.first.name());
|
||||
if (over_it != m_overrides.end() && over_it->second != version)
|
||||
{
|
||||
return add_constraint(ref, over_it->second, origin);
|
||||
}
|
||||
auto maybe_scfl = m_ver_provider.get_control_file({ref.first.name(), version});
|
||||
|
||||
if (auto p_scfl = maybe_scfl.get())
|
||||
{
|
||||
auto& exact_ref =
|
||||
ref.second.emplace_node(p_scfl->source_control_file->core_paragraph->version_scheme, version);
|
||||
exact_ref.origins.push_back(origin);
|
||||
|
||||
bool replace;
|
||||
if (exact_ref.scfl == nullptr)
|
||||
{
|
||||
replace = true;
|
||||
}
|
||||
else if (exact_ref.scfl == p_scfl)
|
||||
{
|
||||
replace = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
replace = exact_ref.is_less_than(version);
|
||||
}
|
||||
|
||||
if (replace)
|
||||
{
|
||||
exact_ref.scfl = p_scfl;
|
||||
exact_ref.version = to_version(*p_scfl->source_control_file);
|
||||
exact_ref.deps.clear();
|
||||
|
||||
add_feature_to(ref, exact_ref, "core");
|
||||
|
||||
for (auto&& f : ref.second.features)
|
||||
{
|
||||
add_feature_to(ref, exact_ref, f);
|
||||
}
|
||||
|
||||
if (ref.second.default_features)
|
||||
{
|
||||
for (auto&& f : p_scfl->source_control_file->core_paragraph->default_features)
|
||||
{
|
||||
add_feature_to(ref, exact_ref, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errors.push_back(maybe_scfl.error());
|
||||
}
|
||||
}
|
||||
|
||||
void VersionedPackageGraph::add_constraint(std::pair<const PackageSpec, PackageNode>& ref,
|
||||
const std::string& feature,
|
||||
const std::string& origin)
|
||||
{
|
||||
auto inserted = ref.second.features.emplace(feature).second;
|
||||
if (inserted)
|
||||
{
|
||||
if (auto relaxed = ref.second.relaxed.get())
|
||||
{
|
||||
add_feature_to(ref, **relaxed, feature);
|
||||
}
|
||||
for (auto&& vsi : ref.second.exacts)
|
||||
{
|
||||
add_feature_to(ref, vsi.second, feature);
|
||||
}
|
||||
}
|
||||
(void)origin;
|
||||
}
|
||||
|
||||
std::pair<const PackageSpec, VersionedPackageGraph::PackageNode>& VersionedPackageGraph::emplace_package(
|
||||
const PackageSpec& spec)
|
||||
{
|
||||
return *m_graph.emplace(spec, PackageNode{}).first;
|
||||
}
|
||||
|
||||
static Optional<Versions::Version> dep_to_version(const std::string& name,
|
||||
const DependencyConstraint& dc,
|
||||
const PortFileProvider::IBaselineProvider& base_provider)
|
||||
{
|
||||
auto maybe_cons = to_version(dc);
|
||||
if (maybe_cons)
|
||||
{
|
||||
return maybe_cons;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base_provider.get_baseline_version(name);
|
||||
}
|
||||
}
|
||||
|
||||
void VersionedPackageGraph::add_override(const std::string& name, const Versions::Version& v)
|
||||
{
|
||||
m_overrides.emplace(name, v);
|
||||
}
|
||||
|
||||
void VersionedPackageGraph::add_roots(View<Dependency> deps, const PackageSpec& toplevel)
|
||||
{
|
||||
auto specs = Util::fmap(deps, [&toplevel](const Dependency& d) {
|
||||
return PackageSpec{d.name, toplevel.triplet()};
|
||||
});
|
||||
specs.push_back(toplevel);
|
||||
Util::sort_unique_erase(specs);
|
||||
m_var_provider.load_dep_info_vars(specs);
|
||||
const auto& vars = m_var_provider.get_dep_info_vars(toplevel).value_or_exit(VCPKG_LINE_INFO);
|
||||
std::vector<const Dependency*> active_deps;
|
||||
|
||||
for (auto&& dep : deps)
|
||||
{
|
||||
PackageSpec spec(dep.name, toplevel.triplet());
|
||||
if (!dep.platform.evaluate(vars)) continue;
|
||||
|
||||
active_deps.push_back(&dep);
|
||||
|
||||
// Disable default features for deps with [core] as a dependency
|
||||
// Note: x[core], x[y] will still eventually depend on defaults due to the second x[y]
|
||||
if (Util::find(dep.features, "core") != dep.features.end())
|
||||
{
|
||||
auto& node = emplace_package(spec);
|
||||
node.second.default_features = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto pdep : active_deps)
|
||||
{
|
||||
const auto& dep = *pdep;
|
||||
PackageSpec spec(dep.name, toplevel.triplet());
|
||||
|
||||
auto& node = emplace_package(spec);
|
||||
|
||||
auto over_it = m_overrides.find(dep.name);
|
||||
if (over_it != m_overrides.end())
|
||||
{
|
||||
m_roots.push_back(DepSpec{spec, over_it->second, dep.features});
|
||||
add_constraint(node, over_it->second, toplevel.name());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dep_ver = to_version(dep.constraint);
|
||||
auto base_ver = m_base_provider.get_baseline_version(dep.name);
|
||||
if (auto p_dep_ver = dep_ver.get())
|
||||
{
|
||||
m_roots.push_back(DepSpec{spec, *p_dep_ver, dep.features});
|
||||
if (auto p_base_ver = base_ver.get())
|
||||
{
|
||||
// Compare version constraint with baseline to only evaluate the "tighter" constraint
|
||||
auto dep_scfl = m_ver_provider.get_control_file({dep.name, *p_dep_ver});
|
||||
auto base_scfl = m_ver_provider.get_control_file({dep.name, *p_base_ver});
|
||||
if (dep_scfl && base_scfl)
|
||||
{
|
||||
auto r =
|
||||
compare_versions(dep_scfl.get()->source_control_file->core_paragraph->version_scheme,
|
||||
*p_dep_ver,
|
||||
base_scfl.get()->source_control_file->core_paragraph->version_scheme,
|
||||
*p_base_ver);
|
||||
if (r == VerComp::lt)
|
||||
{
|
||||
add_constraint(node, *p_base_ver, "baseline");
|
||||
add_constraint(node, *p_dep_ver, toplevel.name());
|
||||
}
|
||||
else
|
||||
{
|
||||
add_constraint(node, *p_dep_ver, toplevel.name());
|
||||
add_constraint(node, *p_base_ver, "baseline");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!dep_scfl) m_errors.push_back(dep_scfl.error());
|
||||
if (!base_scfl) m_errors.push_back(base_scfl.error());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
add_constraint(node, *p_dep_ver, toplevel.name());
|
||||
}
|
||||
}
|
||||
else if (auto p_base_ver = base_ver.get())
|
||||
{
|
||||
m_roots.push_back(DepSpec{spec, *p_base_ver, dep.features});
|
||||
add_constraint(node, *p_base_ver, toplevel.name());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errors.push_back(Strings::concat("Cannot resolve unversioned dependency from top-level to ",
|
||||
dep.name,
|
||||
" without a baseline entry or override."));
|
||||
}
|
||||
|
||||
for (auto&& f : dep.features)
|
||||
{
|
||||
add_constraint(node, f, toplevel.name());
|
||||
}
|
||||
if (Util::find(dep.features, "core") == dep.features.end())
|
||||
{
|
||||
add_constraint_default_features(node, toplevel.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExpectedS<ActionPlan> VersionedPackageGraph::finalize_extract_plan()
|
||||
{
|
||||
if (m_errors.size() > 0)
|
||||
{
|
||||
return Strings::join("\n", m_errors);
|
||||
}
|
||||
|
||||
ActionPlan ret;
|
||||
|
||||
// second == nullptr means "in progress"
|
||||
std::map<PackageSpec, VersionSchemeInfo*> emitted;
|
||||
struct Frame
|
||||
{
|
||||
InstallPlanAction ipa;
|
||||
std::vector<DepSpec> deps;
|
||||
};
|
||||
std::vector<Frame> stack;
|
||||
|
||||
auto push = [&emitted, this, &stack](const PackageSpec& spec,
|
||||
const Versions::Version& new_ver) -> Optional<std::string> {
|
||||
auto&& node = m_graph[spec];
|
||||
auto over_it = m_overrides.find(spec.name());
|
||||
|
||||
VersionedPackageGraph::VersionSchemeInfo* p_vnode;
|
||||
if (over_it != m_overrides.end())
|
||||
p_vnode = node.get_node(over_it->second);
|
||||
else
|
||||
p_vnode = node.get_node(new_ver);
|
||||
|
||||
if (!p_vnode) return Strings::concat("Version was not found during discovery: ", spec, "@", new_ver);
|
||||
|
||||
auto p = emitted.emplace(spec, nullptr);
|
||||
if (p.second)
|
||||
{
|
||||
// Newly inserted
|
||||
if (over_it == m_overrides.end())
|
||||
{
|
||||
// Not overridden -- Compare against baseline
|
||||
if (auto baseline = m_base_provider.get_baseline_version(spec.name()))
|
||||
{
|
||||
if (auto base_node = node.get_node(*baseline.get()))
|
||||
{
|
||||
if (base_node != p_vnode)
|
||||
{
|
||||
return Strings::concat("Version conflict on ",
|
||||
spec.name(),
|
||||
"@",
|
||||
new_ver,
|
||||
": baseline required ",
|
||||
*baseline.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -> Add stack frame
|
||||
auto maybe_vars = m_var_provider.get_dep_info_vars(spec);
|
||||
|
||||
InstallPlanAction ipa(spec, *p_vnode->scfl, RequestType::USER_REQUESTED, std::move(p_vnode->deps));
|
||||
std::vector<DepSpec> deps;
|
||||
for (auto&& f : ipa.feature_list)
|
||||
{
|
||||
if (auto maybe_deps =
|
||||
p_vnode->scfl->source_control_file->find_dependencies_for_feature(f).get())
|
||||
{
|
||||
for (auto&& dep : *maybe_deps)
|
||||
{
|
||||
if (dep.name == spec.name()) continue;
|
||||
|
||||
if (!dep.platform.is_empty() &&
|
||||
!dep.platform.evaluate(maybe_vars.value_or_exit(VCPKG_LINE_INFO)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto maybe_cons = dep_to_version(dep.name, dep.constraint, m_base_provider);
|
||||
|
||||
if (auto cons = maybe_cons.get())
|
||||
{
|
||||
deps.emplace_back(DepSpec{{dep.name, spec.triplet()}, std::move(*cons)});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Strings::concat("Cannot resolve unconstrained dependency from ",
|
||||
spec.name(),
|
||||
" to ",
|
||||
dep.name,
|
||||
" without a baseline entry or override.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.push_back(Frame{std::move(ipa), std::move(deps)});
|
||||
return nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
// spec already present in map
|
||||
if (p.first->second == nullptr)
|
||||
{
|
||||
return Strings::concat(
|
||||
"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 nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto&& root : m_roots)
|
||||
{
|
||||
if (auto err = push(root.spec, root.ver))
|
||||
{
|
||||
return std::move(*err.get());
|
||||
}
|
||||
|
||||
while (stack.size() > 0)
|
||||
{
|
||||
auto& back = stack.back();
|
||||
if (back.deps.empty())
|
||||
{
|
||||
emitted[back.ipa.spec] = m_graph[back.ipa.spec].get_node(
|
||||
to_version(*back.ipa.source_control_file_location.get()->source_control_file));
|
||||
ret.install_actions.push_back(std::move(back.ipa));
|
||||
stack.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dep = std::move(back.deps.back());
|
||||
back.deps.pop_back();
|
||||
if (auto err = push(dep.spec, dep.ver))
|
||||
{
|
||||
return std::move(*err.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ExpectedS<ActionPlan> create_versioned_install_plan(const PortFileProvider::IVersionedPortfileProvider& provider,
|
||||
const PortFileProvider::IBaselineProvider& bprovider,
|
||||
const CMakeVars::CMakeVarProvider& var_provider,
|
||||
const std::vector<Dependency>& deps,
|
||||
const std::vector<DependencyOverride>& overrides,
|
||||
const PackageSpec& toplevel)
|
||||
{
|
||||
VersionedPackageGraph vpg(provider, bprovider, var_provider);
|
||||
for (auto&& o : overrides)
|
||||
vpg.add_override(o.name, {o.version, o.port_version});
|
||||
vpg.add_roots(deps, toplevel);
|
||||
return vpg.finalize_extract_plan();
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,7 @@ namespace vcpkg
|
||||
if (lhs.features != rhs.features) return false;
|
||||
if (!structurally_equal(lhs.platform, rhs.platform)) return false;
|
||||
if (lhs.extra_info != rhs.extra_info) return false;
|
||||
if (lhs.constraint != rhs.constraint) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ namespace vcpkg::PortFileProvider
|
||||
auto port = port_name.to_string();
|
||||
for (auto&& version_entry : version_entries)
|
||||
{
|
||||
VersionSpec spec(port, version_entry.version, version_entry.scheme);
|
||||
VersionSpec spec(port, version_entry.version);
|
||||
m_impl->versions_cache[port].push_back(spec);
|
||||
m_impl->git_tree_cache.emplace(std::move(spec), std::move(version_entry.git_tree));
|
||||
}
|
||||
|
@ -2,22 +2,19 @@
|
||||
|
||||
namespace vcpkg::Versions
|
||||
{
|
||||
VersionSpec::VersionSpec(const std::string& port_name, const VersionT& version, Scheme scheme)
|
||||
: port_name(port_name), version(version), scheme(scheme)
|
||||
VersionSpec::VersionSpec(const std::string& port_name, const VersionT& version)
|
||||
: port_name(port_name), version(version)
|
||||
{
|
||||
}
|
||||
|
||||
VersionSpec::VersionSpec(const std::string& port_name,
|
||||
const std::string& version_string,
|
||||
int port_version,
|
||||
Scheme scheme)
|
||||
: port_name(port_name), version(version_string, port_version), scheme(scheme)
|
||||
VersionSpec::VersionSpec(const std::string& port_name, const std::string& version_string, int port_version)
|
||||
: port_name(port_name), version(version_string, port_version)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const VersionSpec& lhs, const VersionSpec& rhs)
|
||||
{
|
||||
return std::tie(lhs.port_name, lhs.version, lhs.scheme) == std::tie(rhs.port_name, rhs.version, rhs.scheme);
|
||||
return std::tie(lhs.port_name, lhs.version) == std::tie(rhs.port_name, rhs.version);
|
||||
}
|
||||
|
||||
bool operator!=(const VersionSpec& lhs, const VersionSpec& rhs) { return !(lhs == rhs); }
|
||||
@ -28,7 +25,6 @@ namespace vcpkg::Versions
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
|
||||
return ((hash<string>()(key.port_name) ^ (hash<string>()(key.version.to_string()) << 1)) >> 1) ^
|
||||
(hash<int>()(static_cast<int>(key.scheme)) << 1);
|
||||
return hash<string>()(key.port_name) ^ (hash<string>()(key.version.to_string()) >> 1);
|
||||
}
|
||||
}
|
@ -4,24 +4,28 @@
|
||||
|
||||
namespace vcpkg
|
||||
{
|
||||
VersionT::VersionT() noexcept : value("0.0.0"), port_version(0) { }
|
||||
VersionT::VersionT(std::string&& value, int port_version) : value(std::move(value)), port_version(port_version) { }
|
||||
VersionT::VersionT(const std::string& value, int port_version) : value(value), port_version(port_version) { }
|
||||
|
||||
std::string VersionT::to_string() const
|
||||
VersionT::VersionT() noexcept : m_text("0.0.0"), m_port_version(0) { }
|
||||
VersionT::VersionT(std::string&& value, int port_version) : m_text(std::move(value)), m_port_version(port_version)
|
||||
{
|
||||
return port_version == 0 ? value : Strings::format("%s#%d", value, port_version);
|
||||
}
|
||||
VersionT::VersionT(const std::string& value, int port_version) : m_text(value), m_port_version(port_version) { }
|
||||
|
||||
std::string VersionT::to_string() const { return Strings::concat(*this); }
|
||||
void VersionT::to_string(std::string& out) const
|
||||
{
|
||||
out.append(m_text);
|
||||
if (m_port_version) Strings::append(out, '#', m_port_version);
|
||||
}
|
||||
|
||||
bool operator==(const VersionT& left, const VersionT& right)
|
||||
{
|
||||
return left.port_version == right.port_version && left.value == right.value;
|
||||
return left.m_port_version == right.m_port_version && left.m_text == right.m_text;
|
||||
}
|
||||
bool operator!=(const VersionT& left, const VersionT& right) { return !(left == right); }
|
||||
|
||||
bool VersionTMapLess::operator()(const VersionT& left, const VersionT& right) const
|
||||
{
|
||||
auto cmp = left.value.compare(right.value);
|
||||
auto cmp = left.m_text.compare(right.m_text);
|
||||
if (cmp < 0)
|
||||
{
|
||||
return true;
|
||||
@ -31,7 +35,7 @@ namespace vcpkg
|
||||
return false;
|
||||
}
|
||||
|
||||
return left.port_version < right.port_version;
|
||||
return left.m_port_version < right.m_port_version;
|
||||
}
|
||||
|
||||
VersionDiff::VersionDiff() noexcept : left(), right() { }
|
||||
|
Loading…
x
Reference in New Issue
Block a user