[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
This commit is contained in:
Victor Romero 2020-12-07 09:10:23 -08:00 committed by GitHub
parent 066c6fd712
commit 53e6588e9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 803 additions and 26 deletions

View File

@ -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')

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}