aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Romero <romerosanchezv@gmail.com>2020-12-07 09:10:23 -0800
committerGitHub <noreply@github.com>2020-12-07 09:10:23 -0800
commit53e6588e9d4529033092c9adb43e317968b23712 (patch)
treebc5a9147306095438899c88aee48b43c5ffde539
parent066c6fd712a3b7015388de644e44faf9774f3641 (diff)
downloadvcpkg-53e6588e9d4529033092c9adb43e317968b23712.tar.gz
vcpkg-53e6588e9d4529033092c9adb43e317968b23712.zip
[vcpkg] Add SemVer and Date versioning schemes (#14889)
* [vcpkg] Add semver versioning scheme * Remove unnecessary code * Fix SemVer comparison and add sorting test * Add date scheme * PR comments * Use a different column for date and semver schemes. * Use locale agnostic conversion to long * Add tests for version scheme change * Validate version strings before parsing * Format * Improve error messages * PR comments * PR comments pt. 2
-rw-r--r--scripts/generateBaseline.py50
-rw-r--r--toolsrc/include/vcpkg/versions.h44
-rw-r--r--toolsrc/src/vcpkg-test/dependencies.cpp456
-rw-r--r--toolsrc/src/vcpkg/dependencies.cpp62
-rw-r--r--toolsrc/src/vcpkg/versions.cpp217
5 files changed, 803 insertions, 26 deletions
diff --git a/scripts/generateBaseline.py b/scripts/generateBaseline.py
new file mode 100644
index 000000000..45c424a7d
--- /dev/null
+++ b/scripts/generateBaseline.py
@@ -0,0 +1,50 @@
+import os
+import json
+import subprocess
+import sys
+
+SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
+
+
+def generate_baseline(ports_path, output_filepath):
+ port_names = [item for item in os.listdir(
+ ports_path) if os.path.isdir(os.path.join(ports_path, item))]
+ port_names.sort()
+
+ total = len(port_names)
+ baseline_versions = {}
+ for counter, port_name in enumerate(port_names):
+ vcpkg_exe = os.path.join(SCRIPT_DIRECTORY, '../vcpkg')
+ print(f'[{counter + 1}/{total}] Getting package info for {port_name}')
+ output = subprocess.run(
+ [vcpkg_exe, 'x-package-info', '--x-json', port_name],
+ capture_output=True,
+ encoding='utf-8')
+
+ if output.returncode == 0:
+ package_info = json.loads(output.stdout)
+ port_info = package_info['results'][port_name]
+
+ version = {}
+ for scheme in ['version-string', 'version-semver', 'version-date', 'version']:
+ if scheme in port_info:
+ version[scheme] = package_info['results'][port_name][scheme]
+ break
+ version['port-version'] = 0
+ if 'port-version' in port_info:
+ version['port-version'] = port_info['port-version']
+ baseline_versions[port_name] = version
+ else:
+ print(f'x-package-info --x-json {port_name} failed: ', output.stdout.strip(), file=sys.stderr)
+
+ output = {}
+ output['default'] = baseline_versions
+
+ with open(output_filepath, 'r') as output_file:
+ json.dump(baseline_versions, output_file)
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ generate_baseline(
+ ports_path=f'{SCRIPT_DIRECTORY}/../ports', output_filepath='baseline.json')
diff --git a/toolsrc/include/vcpkg/versions.h b/toolsrc/include/vcpkg/versions.h
index 09df15366..19b5546ea 100644
--- a/toolsrc/include/vcpkg/versions.h
+++ b/toolsrc/include/vcpkg/versions.h
@@ -6,6 +6,14 @@ namespace vcpkg::Versions
{
using Version = VersionT;
+ enum class VerComp
+ {
+ unk,
+ lt,
+ eq,
+ gt,
+ };
+
enum class Scheme
{
Relaxed,
@@ -32,6 +40,42 @@ namespace vcpkg::Versions
std::size_t operator()(const VersionSpec& key) const;
};
+ struct RelaxedVersion
+ {
+ std::string original_string;
+ std::vector<uint64_t> version;
+
+ static ExpectedS<RelaxedVersion> from_string(const std::string& str);
+ };
+
+ struct SemanticVersion
+ {
+ std::string original_string;
+ std::string version_string;
+ std::string prerelease_string;
+
+ std::vector<uint64_t> version;
+ std::vector<std::string> identifiers;
+
+ static ExpectedS<SemanticVersion> from_string(const std::string& str);
+ };
+
+ struct DateVersion
+ {
+ std::string original_string;
+ std::string version_string;
+ std::string identifiers_string;
+
+ std::vector<uint64_t> identifiers;
+
+ static ExpectedS<DateVersion> from_string(const std::string& str);
+ };
+
+ VerComp compare(const std::string& a, const std::string& b, Scheme scheme);
+ VerComp compare(const RelaxedVersion& a, const RelaxedVersion& b);
+ VerComp compare(const SemanticVersion& a, const SemanticVersion& b);
+ VerComp compare(const DateVersion& a, const DateVersion& b);
+
struct Constraint
{
enum class Type
diff --git a/toolsrc/src/vcpkg-test/dependencies.cpp b/toolsrc/src/vcpkg-test/dependencies.cpp
index bcc2f14a0..2aae7fffa 100644
--- a/toolsrc/src/vcpkg-test/dependencies.cpp
+++ b/toolsrc/src/vcpkg-test/dependencies.cpp
@@ -121,6 +121,42 @@ static void check_name_and_version(const Dependencies::InstallPlanAction& ipa,
}
}
+static void check_semver_version(const ExpectedS<Versions::SemanticVersion>& maybe_version,
+ const std::string& version_string,
+ const std::string& prerelease_string,
+ uint64_t major,
+ uint64_t minor,
+ uint64_t patch,
+ const std::vector<std::string>& identifiers)
+{
+ auto actual_version = unwrap(maybe_version);
+ CHECK(actual_version.version_string == version_string);
+ CHECK(actual_version.prerelease_string == prerelease_string);
+ REQUIRE(actual_version.version.size() == 3);
+ CHECK(actual_version.version[0] == major);
+ CHECK(actual_version.version[1] == minor);
+ CHECK(actual_version.version[2] == patch);
+ CHECK(actual_version.identifiers == identifiers);
+}
+
+static void check_relaxed_version(const ExpectedS<Versions::RelaxedVersion>& maybe_version,
+ const std::vector<uint64_t>& version)
+{
+ auto actual_version = unwrap(maybe_version);
+ CHECK(actual_version.version == version);
+}
+
+static void check_date_version(const ExpectedS<Versions::DateVersion>& maybe_version,
+ const std::string& version_string,
+ const std::string& identifiers_string,
+ const std::vector<uint64_t>& identifiers)
+{
+ auto actual_version = unwrap(maybe_version);
+ CHECK(actual_version.version_string == version_string);
+ CHECK(actual_version.identifiers_string == identifiers_string);
+ CHECK(actual_version.identifiers == identifiers);
+}
+
static const PackageSpec& toplevel_spec()
{
static const PackageSpec ret("toplevel-spec", Test::X86_WINDOWS);
@@ -506,6 +542,426 @@ TEST_CASE ("version install diamond relaxed", "[versionplan]")
check_name_and_version(install_plan.install_actions[2], "a", {"3", 0});
}
+TEST_CASE ("version parse semver", "[versionplan]")
+{
+ auto version_basic = Versions::SemanticVersion::from_string("1.2.3");
+ check_semver_version(version_basic, "1.2.3", "", 1, 2, 3, {});
+
+ auto version_simple_tag = Versions::SemanticVersion::from_string("1.0.0-alpha");
+ check_semver_version(version_simple_tag, "1.0.0", "alpha", 1, 0, 0, {"alpha"});
+
+ auto version_alphanumeric_tag = Versions::SemanticVersion::from_string("1.0.0-0alpha0");
+ check_semver_version(version_alphanumeric_tag, "1.0.0", "0alpha0", 1, 0, 0, {"0alpha0"});
+
+ auto version_complex_tag = Versions::SemanticVersion::from_string("1.0.0-alpha.1.0.0");
+ check_semver_version(version_complex_tag, "1.0.0", "alpha.1.0.0", 1, 0, 0, {"alpha", "1", "0", "0"});
+
+ auto version_complexer_tag = Versions::SemanticVersion::from_string("1.0.0-alpha.1.x.y.z.0-alpha.0-beta.l-a-s-t");
+ check_semver_version(version_complexer_tag,
+ "1.0.0",
+ "alpha.1.x.y.z.0-alpha.0-beta.l-a-s-t",
+ 1,
+ 0,
+ 0,
+ {"alpha", "1", "x", "y", "z", "0-alpha", "0-beta", "l-a-s-t"});
+
+ auto version_ridiculous_tag = Versions::SemanticVersion::from_string("1.0.0----------------------------------");
+ check_semver_version(version_ridiculous_tag,
+ "1.0.0",
+ "---------------------------------",
+ 1,
+ 0,
+ 0,
+ {"---------------------------------"});
+
+ auto version_build_tag = Versions::SemanticVersion::from_string("1.0.0+build");
+ check_semver_version(version_build_tag, "1.0.0", "", 1, 0, 0, {});
+
+ auto version_prerelease_build_tag = Versions::SemanticVersion::from_string("1.0.0-alpha+build");
+ check_semver_version(version_prerelease_build_tag, "1.0.0", "alpha", 1, 0, 0, {"alpha"});
+
+ auto version_invalid_incomplete = Versions::SemanticVersion::from_string("1.0-alpha");
+ CHECK(!version_invalid_incomplete.has_value());
+
+ auto version_invalid_leading_zeroes = Versions::SemanticVersion::from_string("1.02.03-alpha+build");
+ CHECK(!version_invalid_leading_zeroes.has_value());
+
+ auto version_invalid_leading_zeroes_in_tag = Versions::SemanticVersion::from_string("1.0.0-01");
+ CHECK(!version_invalid_leading_zeroes_in_tag.has_value());
+
+ auto version_invalid_characters = Versions::SemanticVersion::from_string("1.0.0-alpha#2");
+ CHECK(!version_invalid_characters.has_value());
+}
+
+TEST_CASE ("version parse relaxed", "[versionplan]")
+{
+ auto version_basic = Versions::RelaxedVersion::from_string("1.2.3");
+ check_relaxed_version(version_basic, {1, 2, 3});
+
+ auto version_short = Versions::RelaxedVersion::from_string("1");
+ check_relaxed_version(version_short, {1});
+
+ auto version_long =
+ Versions::RelaxedVersion::from_string("1.20.300.4000.50000.6000000.70000000.80000000.18446744073709551610");
+ check_relaxed_version(version_long, {1, 20, 300, 4000, 50000, 6000000, 70000000, 80000000, 18446744073709551610});
+
+ auto version_invalid_characters = Versions::RelaxedVersion::from_string("1.a.0");
+ CHECK(!version_invalid_characters.has_value());
+
+ auto version_invalid_identifiers_2 = Versions::RelaxedVersion::from_string("1.1a.2");
+ CHECK(!version_invalid_identifiers_2.has_value());
+
+ auto version_invalid_leading_zeroes = Versions::RelaxedVersion::from_string("01.002.003");
+ CHECK(!version_invalid_leading_zeroes.has_value());
+}
+
+TEST_CASE ("version parse date", "[versionplan]")
+{
+ auto version_basic = Versions::DateVersion::from_string("2020-12-25");
+ check_date_version(version_basic, "2020-12-25", "", {});
+
+ auto version_identifiers = Versions::DateVersion::from_string("2020-12-25.1.2.3");
+ check_date_version(version_identifiers, "2020-12-25", "1.2.3", {1, 2, 3});
+
+ auto version_invalid_date = Versions::DateVersion::from_string("2020-1-1");
+ CHECK(!version_invalid_date.has_value());
+
+ auto version_invalid_identifiers = Versions::DateVersion::from_string("2020-01-01.alpha");
+ CHECK(!version_invalid_identifiers.has_value());
+
+ auto version_invalid_identifiers_2 = Versions::DateVersion::from_string("2020-01-01.2a");
+ CHECK(!version_invalid_identifiers_2.has_value());
+
+ auto version_invalid_leading_zeroes = Versions::DateVersion::from_string("2020-01-01.01");
+ CHECK(!version_invalid_leading_zeroes.has_value());
+}
+
+TEST_CASE ("version sort semver", "[versionplan]")
+{
+ std::vector<Versions::SemanticVersion> versions{unwrap(Versions::SemanticVersion::from_string("1.0.0")),
+ unwrap(Versions::SemanticVersion::from_string("0.0.0")),
+ unwrap(Versions::SemanticVersion::from_string("1.1.0")),
+ unwrap(Versions::SemanticVersion::from_string("2.0.0")),
+ unwrap(Versions::SemanticVersion::from_string("1.1.1")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.1")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-alpha.1")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-beta")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-alpha")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-alpha.beta")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-rc")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-beta.2")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-beta.20")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-beta.3")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-1")),
+ unwrap(Versions::SemanticVersion::from_string("1.0.0-0alpha"))};
+
+ std::sort(std::begin(versions), std::end(versions), [](const auto& lhs, const auto& rhs) -> bool {
+ return Versions::compare(lhs, rhs) == Versions::VerComp::lt;
+ });
+
+ CHECK(versions[0].original_string == "0.0.0");
+ CHECK(versions[1].original_string == "1.0.0-1");
+ CHECK(versions[2].original_string == "1.0.0-0alpha");
+ CHECK(versions[3].original_string == "1.0.0-alpha");
+ CHECK(versions[4].original_string == "1.0.0-alpha.1");
+ CHECK(versions[5].original_string == "1.0.0-alpha.beta");
+ CHECK(versions[6].original_string == "1.0.0-beta");
+ CHECK(versions[7].original_string == "1.0.0-beta.2");
+ CHECK(versions[8].original_string == "1.0.0-beta.3");
+ CHECK(versions[9].original_string == "1.0.0-beta.20");
+ CHECK(versions[10].original_string == "1.0.0-rc");
+ CHECK(versions[11].original_string == "1.0.0");
+ CHECK(versions[12].original_string == "1.0.1");
+ CHECK(versions[13].original_string == "1.1.0");
+ CHECK(versions[14].original_string == "1.1.1");
+ CHECK(versions[15].original_string == "2.0.0");
+}
+
+TEST_CASE ("version sort relaxed", "[versionplan]")
+{
+ std::vector<Versions::RelaxedVersion> versions{unwrap(Versions::RelaxedVersion::from_string("1.0.0")),
+ unwrap(Versions::RelaxedVersion::from_string("1.0")),
+ unwrap(Versions::RelaxedVersion::from_string("1")),
+ unwrap(Versions::RelaxedVersion::from_string("2")),
+ unwrap(Versions::RelaxedVersion::from_string("1.1")),
+ unwrap(Versions::RelaxedVersion::from_string("1.10.1")),
+ unwrap(Versions::RelaxedVersion::from_string("1.0.1")),
+ unwrap(Versions::RelaxedVersion::from_string("1.0.0.1")),
+ unwrap(Versions::RelaxedVersion::from_string("1.0.0.2"))};
+
+ std::sort(std::begin(versions), std::end(versions), [](const auto& lhs, const auto& rhs) -> bool {
+ return Versions::compare(lhs, rhs) == Versions::VerComp::lt;
+ });
+
+ CHECK(versions[0].original_string == "1");
+ CHECK(versions[1].original_string == "1.0");
+ CHECK(versions[2].original_string == "1.0.0");
+ CHECK(versions[3].original_string == "1.0.0.1");
+ CHECK(versions[4].original_string == "1.0.0.2");
+ CHECK(versions[5].original_string == "1.0.1");
+ CHECK(versions[6].original_string == "1.1");
+ CHECK(versions[7].original_string == "1.10.1");
+ CHECK(versions[8].original_string == "2");
+}
+
+TEST_CASE ("version sort date", "[versionplan]")
+{
+ std::vector<Versions::DateVersion> versions{unwrap(Versions::DateVersion::from_string("2021-01-01.2")),
+ unwrap(Versions::DateVersion::from_string("2021-01-01.1")),
+ unwrap(Versions::DateVersion::from_string("2021-01-01.1.1")),
+ unwrap(Versions::DateVersion::from_string("2021-01-01.1.0")),
+ unwrap(Versions::DateVersion::from_string("2021-01-01")),
+ unwrap(Versions::DateVersion::from_string("2021-01-01")),
+ unwrap(Versions::DateVersion::from_string("2020-12-25")),
+ unwrap(Versions::DateVersion::from_string("2020-12-31")),
+ unwrap(Versions::DateVersion::from_string("2021-01-01.10"))};
+
+ std::sort(std::begin(versions), std::end(versions), [](const auto& lhs, const auto& rhs) -> bool {
+ return Versions::compare(lhs, rhs) == Versions::VerComp::lt;
+ });
+
+ CHECK(versions[0].original_string == "2020-12-25");
+ CHECK(versions[1].original_string == "2020-12-31");
+ CHECK(versions[2].original_string == "2021-01-01");
+ CHECK(versions[3].original_string == "2021-01-01");
+ CHECK(versions[4].original_string == "2021-01-01.1");
+ CHECK(versions[5].original_string == "2021-01-01.1.0");
+ CHECK(versions[6].original_string == "2021-01-01.1.1");
+ CHECK(versions[7].original_string == "2021-01-01.2");
+ CHECK(versions[8].original_string == "2021-01-01.10");
+}
+
+TEST_CASE ("version install simple semver", "[versionplan]")
+{
+ MockBaselineProvider bp;
+ bp.v["a"] = {"2.0.0", 0};
+
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"2.0.0", 0}, Scheme::Semver);
+ vp.emplace("a", {"3.0.0", 0}, Scheme::Semver);
+
+ MockCMakeVarProvider var_provider;
+
+ auto install_plan = unwrap(Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {
+ Dependency{"a", {}, {}, {Constraint::Type::Minimum, "3.0.0", 0}},
+ },
+ {},
+ toplevel_spec()));
+
+ REQUIRE(install_plan.size() == 1);
+ check_name_and_version(install_plan.install_actions[0], "a", {"3.0.0", 0});
+}
+
+TEST_CASE ("version install transitive semver", "[versionplan]")
+{
+ MockBaselineProvider bp;
+ bp.v["a"] = {"2.0.0", 0};
+ bp.v["b"] = {"2.0.0", 0};
+
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"2.0.0", 0}, Scheme::Semver);
+ vp.emplace("a", {"3.0.0", 0}, Scheme::Semver).source_control_file->core_paragraph->dependencies = {
+ Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "3.0.0"}},
+ };
+ vp.emplace("b", {"2.0.0", 0}, Scheme::Semver);
+ vp.emplace("b", {"3.0.0", 0}, Scheme::Semver);
+
+ MockCMakeVarProvider var_provider;
+
+ auto install_plan = unwrap(Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {
+ Dependency{"a", {}, {}, {Constraint::Type::Minimum, "3.0.0", 0}},
+ },
+ {},
+ toplevel_spec()));
+
+ REQUIRE(install_plan.size() == 2);
+ check_name_and_version(install_plan.install_actions[0], "b", {"3.0.0", 0});
+ check_name_and_version(install_plan.install_actions[1], "a", {"3.0.0", 0});
+}
+
+TEST_CASE ("version install diamond semver", "[versionplan]")
+{
+ MockBaselineProvider bp;
+ bp.v["a"] = {"2.0.0", 0};
+ bp.v["b"] = {"3.0.0", 0};
+
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"2.0.0", 0}, Scheme::Semver);
+ vp.emplace("a", {"3.0.0", 0}, Scheme::Semver).source_control_file->core_paragraph->dependencies = {
+ Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "2.0.0", 1}},
+ Dependency{"c", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "5.0.0", 1}},
+ };
+ vp.emplace("b", {"2.0.0", 1}, Scheme::Semver);
+ vp.emplace("b", {"3.0.0", 0}, Scheme::Semver).source_control_file->core_paragraph->dependencies = {
+ Dependency{"c", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "9.0.0", 2}},
+ };
+ vp.emplace("c", {"5.0.0", 1}, Scheme::Semver);
+ vp.emplace("c", {"9.0.0", 2}, Scheme::Semver);
+
+ MockCMakeVarProvider var_provider;
+
+ auto install_plan = unwrap(Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {
+ Dependency{"a", {}, {}, {Constraint::Type::Minimum, "3.0.0", 0}},
+ Dependency{"b", {}, {}, {Constraint::Type::Minimum, "2.0.0", 1}},
+ },
+ {},
+ toplevel_spec()));
+
+ REQUIRE(install_plan.size() == 3);
+ check_name_and_version(install_plan.install_actions[0], "c", {"9.0.0", 2});
+ check_name_and_version(install_plan.install_actions[1], "b", {"3.0.0", 0});
+ check_name_and_version(install_plan.install_actions[2], "a", {"3.0.0", 0});
+}
+
+TEST_CASE ("version install simple date", "[versionplan]")
+{
+ MockBaselineProvider bp;
+ bp.v["a"] = {"2020-02-01", 0};
+
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"2020-02-01", 0}, Scheme::Date);
+ vp.emplace("a", {"2020-03-01", 0}, Scheme::Date);
+
+ MockCMakeVarProvider var_provider;
+
+ auto install_plan = unwrap(Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {
+ Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2020-03-01", 0}},
+ },
+ {},
+ toplevel_spec()));
+
+ REQUIRE(install_plan.size() == 1);
+ check_name_and_version(install_plan.install_actions[0], "a", {"2020-03-01", 0});
+}
+
+TEST_CASE ("version install transitive date", "[versionplan]")
+{
+ MockBaselineProvider bp;
+ bp.v["a"] = {"2020-01-01.2", 0};
+ bp.v["b"] = {"2020-01-01.3", 0};
+
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"2020-01-01.2", 0}, Scheme::Date);
+ vp.emplace("a", {"2020-01-01.3", 0}, Scheme::Date).source_control_file->core_paragraph->dependencies = {
+ Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "2020-01-01.3"}},
+ };
+ vp.emplace("b", {"2020-01-01.2", 0}, Scheme::Date);
+ vp.emplace("b", {"2020-01-01.3", 0}, Scheme::Date);
+
+ MockCMakeVarProvider var_provider;
+
+ auto install_plan = unwrap(Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {
+ Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2020-01-01.3", 0}},
+ },
+ {},
+ toplevel_spec()));
+
+ REQUIRE(install_plan.size() == 2);
+ check_name_and_version(install_plan.install_actions[0], "b", {"2020-01-01.3", 0});
+ check_name_and_version(install_plan.install_actions[1], "a", {"2020-01-01.3", 0});
+}
+
+TEST_CASE ("version install diamond date", "[versionplan]")
+{
+ MockBaselineProvider bp;
+ bp.v["a"] = {"2020-01-02", 0};
+ bp.v["b"] = {"2020-01-03", 0};
+
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"2020-01-02", 0}, Scheme::Date);
+ vp.emplace("a", {"2020-01-03", 0}, Scheme::Date).source_control_file->core_paragraph->dependencies = {
+ Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "2020-01-02", 1}},
+ Dependency{"c", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "2020-01-05", 1}},
+ };
+ vp.emplace("b", {"2020-01-02", 1}, Scheme::Date);
+ vp.emplace("b", {"2020-01-03", 0}, Scheme::Date).source_control_file->core_paragraph->dependencies = {
+ Dependency{"c", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "2020-01-09", 2}},
+ };
+ vp.emplace("c", {"2020-01-05", 1}, Scheme::Date);
+ vp.emplace("c", {"2020-01-09", 2}, Scheme::Date);
+
+ MockCMakeVarProvider var_provider;
+
+ auto install_plan = unwrap(Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {
+ Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2020-01-03", 0}},
+ Dependency{"b", {}, {}, {Constraint::Type::Minimum, "2020-01-02", 1}},
+ },
+ {},
+ toplevel_spec()));
+
+ REQUIRE(install_plan.size() == 3);
+ check_name_and_version(install_plan.install_actions[0], "c", {"2020-01-09", 2});
+ check_name_and_version(install_plan.install_actions[1], "b", {"2020-01-03", 0});
+ check_name_and_version(install_plan.install_actions[2], "a", {"2020-01-03", 0});
+}
+
+TEST_CASE ("version install scheme failure", "[versionplan]")
+{
+ MockVersionedPortfileProvider vp;
+ vp.emplace("a", {"1.0.0", 0}, Scheme::Semver);
+ vp.emplace("a", {"1.0.1", 0}, Scheme::Relaxed);
+ vp.emplace("a", {"1.0.2", 0}, Scheme::Semver);
+
+ MockCMakeVarProvider var_provider;
+
+ SECTION ("lower baseline")
+ {
+ MockBaselineProvider bp;
+ bp.v["a"] = {"1.0.0", 0};
+
+ auto install_plan = Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {Dependency{"a", {}, {}, {Constraint::Type::Minimum, "1.0.1", 0}}},
+ {},
+ toplevel_spec());
+
+ REQUIRE(!install_plan.error().empty());
+ CHECK(install_plan.error() == "Version conflict on a@1.0.1: baseline required 1.0.0");
+ }
+ SECTION ("higher baseline")
+ {
+ MockBaselineProvider bp;
+ bp.v["a"] = {"1.0.2", 0};
+
+ auto install_plan = Dependencies::create_versioned_install_plan(
+ vp,
+ bp,
+ var_provider,
+ {Dependency{"a", {}, {}, {Constraint::Type::Minimum, "1.0.1", 0}}},
+ {},
+ toplevel_spec());
+
+ REQUIRE(!install_plan.error().empty());
+ CHECK(install_plan.error() == "Version conflict on a@1.0.1: baseline required 1.0.2");
+ }
+}
+
TEST_CASE ("version install scheme change in port version", "[versionplan]")
{
MockVersionedPortfileProvider vp;
diff --git a/toolsrc/src/vcpkg/dependencies.cpp b/toolsrc/src/vcpkg/dependencies.cpp
index 7d69becdf..e7768f2bf 100644
--- a/toolsrc/src/vcpkg/dependencies.cpp
+++ b/toolsrc/src/vcpkg/dependencies.cpp
@@ -1215,6 +1215,8 @@ namespace vcpkg::Dependencies
std::map<Versions::Version, VersionSchemeInfo*, VersionTMapLess> vermap;
std::map<std::string, VersionSchemeInfo> exacts;
Optional<std::unique_ptr<VersionSchemeInfo>> relaxed;
+ Optional<std::unique_ptr<VersionSchemeInfo>> semver;
+ Optional<std::unique_ptr<VersionSchemeInfo>> date;
std::set<std::string> features;
bool default_features = true;
@@ -1271,6 +1273,30 @@ namespace vcpkg::Dependencies
vsi = relaxed.get()->get();
}
}
+ else if (scheme == Versions::Scheme::Semver)
+ {
+ if (auto p = semver.get())
+ {
+ vsi = p->get();
+ }
+ else
+ {
+ semver = std::make_unique<VersionSchemeInfo>();
+ vsi = semver.get()->get();
+ }
+ }
+ else if (scheme == Versions::Scheme::Date)
+ {
+ if (auto p = date.get())
+ {
+ vsi = p->get();
+ }
+ else
+ {
+ date = std::make_unique<VersionSchemeInfo>();
+ vsi = date.get()->get();
+ }
+ }
else
{
// not implemented
@@ -1287,40 +1313,24 @@ namespace vcpkg::Dependencies
return it == vermap.end() ? nullptr : it->second;
}
- enum class VerComp
- {
- unk,
- lt,
- eq,
- gt,
- };
+ using Versions::VerComp;
+
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)
+
+ if (a.text() != b.text())
{
- 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);
+ auto result = Versions::compare(a.text(), b.text(), sa);
+ if (result != VerComp::eq) return result;
}
+
+ if (a.port_version() < b.port_version()) return VerComp::lt;
+ if (a.port_version() > b.port_version()) return VerComp::gt;
+ return VerComp::eq;
}
bool VersionedPackageGraph::VersionSchemeInfo::is_less_than(const Versions::Version& new_ver) const
diff --git a/toolsrc/src/vcpkg/versions.cpp b/toolsrc/src/vcpkg/versions.cpp
index 7f19813ec..5ea2a8182 100644
--- a/toolsrc/src/vcpkg/versions.cpp
+++ b/toolsrc/src/vcpkg/versions.cpp
@@ -1,7 +1,29 @@
+#include <vcpkg/base/util.h>
+
#include <vcpkg/versions.h>
+#include <regex>
+
namespace vcpkg::Versions
{
+ namespace
+ {
+ Optional<uint64_t> as_numeric(StringView str)
+ {
+ uint64_t res = 0;
+ size_t digits = 0;
+ for (auto&& ch : str)
+ {
+ uint64_t digit_value = static_cast<unsigned char>(ch) - static_cast<unsigned char>('0');
+ if (digit_value > 9) return nullopt;
+ if (res > std::numeric_limits<uint64_t>::max() / 10 - digit_value) return nullopt;
+ ++digits;
+ res = res * 10 + digit_value;
+ }
+ return res;
+ }
+ }
+
VersionSpec::VersionSpec(const std::string& port_name, const VersionT& version)
: port_name(port_name), version(version)
{
@@ -27,4 +49,199 @@ namespace vcpkg::Versions
return hash<string>()(key.port_name) ^ (hash<string>()(key.version.to_string()) >> 1);
}
+
+ ExpectedS<RelaxedVersion> RelaxedVersion::from_string(const std::string& str)
+ {
+ std::regex relaxed_scheme_match("^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*");
+
+ if (!std::regex_match(str, relaxed_scheme_match))
+ {
+ return Strings::format(
+ "Error: String `%s` must only contain dot-separated numeric values without leading zeroes.", str);
+ }
+
+ return RelaxedVersion{str, Util::fmap(Strings::split(str, '.'), [](auto&& strval) -> uint64_t {
+ return as_numeric(strval).value_or_exit(VCPKG_LINE_INFO);
+ })};
+ }
+
+ ExpectedS<SemanticVersion> SemanticVersion::from_string(const std::string& str)
+ {
+ // Suggested regex by semver.org
+ std::regex semver_scheme_match("^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)"
+ "(?:-((?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9][0-9]*|[0-9]"
+ "*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
+ "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
+
+ if (!std::regex_match(str, semver_scheme_match))
+ {
+ return Strings::format(
+ "Error: String `%s` is not a valid Semantic Version string, consult https://semver.org", str);
+ }
+
+ SemanticVersion ret;
+ ret.original_string = str;
+ ret.version_string = str;
+
+ auto build_found = ret.version_string.find('+');
+ if (build_found != std::string::npos)
+ {
+ ret.version_string.resize(build_found);
+ }
+
+ auto prerelease_found = ret.version_string.find('-');
+ if (prerelease_found != std::string::npos)
+ {
+ ret.prerelease_string = ret.version_string.substr(prerelease_found + 1);
+ ret.identifiers = Strings::split(ret.prerelease_string, '.');
+ ret.version_string.resize(prerelease_found);
+ }
+
+ std::regex version_match("(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*)){2}");
+ if (!std::regex_match(ret.version_string, version_match))
+ {
+ return Strings::format("Error: String `%s` does not follow the required MAJOR.MINOR.PATCH format.",
+ ret.version_string);
+ }
+
+ auto parts = Strings::split(ret.version_string, '.');
+ ret.version = Util::fmap(
+ parts, [](auto&& strval) -> uint64_t { return as_numeric(strval).value_or_exit(VCPKG_LINE_INFO); });
+
+ return ret;
+ }
+
+ ExpectedS<DateVersion> DateVersion::from_string(const std::string& str)
+ {
+ std::regex date_scheme_match("([0-9]{4}-[0-9]{2}-[0-9]{2})(\\.(0|[1-9][0-9]*))*");
+ if (!std::regex_match(str, date_scheme_match))
+ {
+ return Strings::format("Error: String `%s` is not a valid date version."
+ "Date section must follow the format YYYY-MM-DD and disambiguators must be "
+ "dot-separated positive integer values without leading zeroes.",
+ str);
+ }
+
+ DateVersion ret;
+ ret.original_string = str;
+ ret.version_string = str;
+
+ auto identifiers_found = ret.version_string.find('.');
+ if (identifiers_found != std::string::npos)
+ {
+ ret.identifiers_string = ret.version_string.substr(identifiers_found + 1);
+ ret.identifiers = Util::fmap(Strings::split(ret.identifiers_string, '.'), [](auto&& strval) -> uint64_t {
+ return as_numeric(strval).value_or_exit(VCPKG_LINE_INFO);
+ });
+ ret.version_string.resize(identifiers_found);
+ }
+
+ return ret;
+ }
+
+ VerComp compare(const std::string& a, const std::string& b, Scheme scheme)
+ {
+ if (scheme == Scheme::String)
+ {
+ return (a == b) ? VerComp::eq : VerComp::unk;
+ }
+ if (scheme == Scheme::Semver)
+ {
+ return compare(SemanticVersion::from_string(a).value_or_exit(VCPKG_LINE_INFO),
+ SemanticVersion::from_string(b).value_or_exit(VCPKG_LINE_INFO));
+ }
+ if (scheme == Scheme::Relaxed)
+ {
+ return compare(RelaxedVersion::from_string(a).value_or_exit(VCPKG_LINE_INFO),
+ RelaxedVersion::from_string(b).value_or_exit(VCPKG_LINE_INFO));
+ }
+ if (scheme == Scheme::Date)
+ {
+ return compare(DateVersion::from_string(a).value_or_exit(VCPKG_LINE_INFO),
+ DateVersion::from_string(b).value_or_exit(VCPKG_LINE_INFO));
+ }
+ Checks::unreachable(VCPKG_LINE_INFO);
+ }
+
+ VerComp compare(const RelaxedVersion& a, const RelaxedVersion& b)
+ {
+ if (a.original_string == b.original_string) return VerComp::eq;
+
+ if (a.version < b.version) return VerComp::lt;
+ if (a.version > b.version) return VerComp::gt;
+ Checks::unreachable(VCPKG_LINE_INFO);
+ }
+
+ VerComp compare(const SemanticVersion& a, const SemanticVersion& b)
+ {
+ if (a.version_string == b.version_string)
+ {
+ if (a.prerelease_string == b.prerelease_string) return VerComp::eq;
+ if (a.prerelease_string.empty()) return VerComp::gt;
+ if (b.prerelease_string.empty()) return VerComp::lt;
+ }
+
+ // Compare version elements left-to-right.
+ if (a.version < b.version) return VerComp::lt;
+ if (a.version > b.version) return VerComp::gt;
+
+ // Compare identifiers left-to-right.
+ auto count = std::min(a.identifiers.size(), b.identifiers.size());
+ for (size_t i = 0; i < count; ++i)
+ {
+ auto&& iden_a = a.identifiers[i];
+ auto&& iden_b = b.identifiers[i];
+
+ auto a_numeric = as_numeric(iden_a);
+ auto b_numeric = as_numeric(iden_b);
+
+ // Numeric identifiers always have lower precedence than non-numeric identifiers.
+ if (a_numeric.has_value() && !b_numeric.has_value()) return VerComp::lt;
+ if (!a_numeric.has_value() && b_numeric.has_value()) return VerComp::gt;
+
+ // Identifiers consisting of only digits are compared numerically.
+ if (a_numeric.has_value() && b_numeric.has_value())
+ {
+ auto a_value = a_numeric.value_or_exit(VCPKG_LINE_INFO);
+ auto b_value = b_numeric.value_or_exit(VCPKG_LINE_INFO);
+
+ if (a_value < b_value) return VerComp::lt;
+ if (a_value > b_value) return VerComp::gt;
+ continue;
+ }
+
+ // Identifiers with letters or hyphens are compared lexically in ASCII sort order.
+ auto strcmp_result = std::strcmp(iden_a.c_str(), iden_b.c_str());
+ if (strcmp_result < 0) return VerComp::lt;
+ if (strcmp_result > 0) return VerComp::gt;
+ }
+
+ // A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding
+ // identifiers are equal.
+ if (a.identifiers.size() < b.identifiers.size()) return VerComp::lt;
+ if (a.identifiers.size() > b.identifiers.size()) return VerComp::gt;
+
+ // This should be unreachable since direct string comparisons of version_string and prerelease_string should
+ // handle this case. If we ever land here, then there's a bug in the the parsing on
+ // SemanticVersion::from_string().
+ Checks::unreachable(VCPKG_LINE_INFO);
+ }
+
+ VerComp compare(const Versions::DateVersion& a, const Versions::DateVersion& b)
+ {
+ if (a.version_string == b.version_string)
+ {
+ if (a.identifiers_string == b.identifiers_string) return VerComp::eq;
+ if (a.identifiers_string.empty() && !b.identifiers_string.empty()) return VerComp::lt;
+ if (!a.identifiers_string.empty() && b.identifiers_string.empty()) return VerComp::gt;
+ }
+
+ // The date parts in our scheme is lexicographically sortable.
+ if (a.version_string < b.version_string) return VerComp::lt;
+ if (a.version_string > b.version_string) return VerComp::gt;
+ if (a.identifiers < b.identifiers) return VerComp::lt;
+ if (a.identifiers > b.identifiers) return VerComp::gt;
+
+ Checks::unreachable(VCPKG_LINE_INFO);
+ }
} \ No newline at end of file