mirror of
https://github.com/microsoft/vcpkg.git
synced 2024-11-24 07:31:37 +08:00
[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:
parent
066c6fd712
commit
53e6588e9d
50
scripts/generateBaseline.py
Normal file
50
scripts/generateBaseline.py
Normal 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')
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user