diff options
| author | ruki <[email protected]> | 2026-08-10 19:51:44 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-08-10 19:51:44 +0800 |
| commit | cd41cf60b38492309b32ac7b1238ef8669a8e3de (patch) | |
| tree | 4e56bfe9b842174c629ed88acb28d1efe1a7121b | |
| parent | ced7f2ae519bc9eca92bcf4f177fe177ddd0e747 (diff) | |
| parent | 30c7ad380873ec4c2e52de15ee8e236dfcd38948 (diff) | |
Merge pull request #7698 from apocelipes/fix-semver-exact-version-selection
fix(semver): correct version selection
| -rw-r--r-- | core/src/xmake/semver/select.c | 120 | ||||
| -rwxr-xr-x | tests/modules/semver/test.lua | 92 |
2 files changed, 184 insertions, 28 deletions
diff --git a/core/src/xmake/semver/select.c b/core/src/xmake/semver/select.c index 283afb050..e95840eab 100644 --- a/core/src/xmake/semver/select.c +++ b/core/src/xmake/semver/select.c @@ -33,6 +33,69 @@ /* ////////////////////////////////////////////////////////////////////////////////////// * private implementation */ +static tb_char_t const *xm_semver_skip_version_prefix(tb_char_t const *version_str, tb_size_t *version_len) { + if (*version_len && (version_str[0] == 'v' || version_str[0] == '=')) { + ++version_str; + --*version_len; + } + return version_str; +} +static tb_bool_t xm_semver_is_exact_version( + tb_char_t const *version_str, tb_size_t version_len, tb_bool_t *is_exact_with_build) { + *is_exact_with_build = tb_false; + version_str = xm_semver_skip_version_prefix(version_str, &version_len); + semver_t version = { 0 }; + if (!semvern(&version, version_str, version_len)) { + *is_exact_with_build = version.build.len > 0; + semver_dtor(&version); + return tb_true; + } + return tb_false; +} +static tb_long_t xm_semver_compare_build(semver_id_t const *left, semver_id_t const *right) { + while (left && right && left->len && right->len) { + if (left->numeric && right->numeric) { + if (left->num != right->num) { + return left->num > right->num ? 1 : -1; + } + } else { + tb_size_t size = left->len < right->len ? left->len : right->len; + tb_long_t result = tb_memcmp(left->raw, right->raw, size); + if (result) { + return result; + } + if (left->len != right->len) { + return left->len > right->len ? 1 : -1; + } + } + left = left->next; + right = right->next; + } + if (left && left->len) { + return 1; + } + if (right && right->len) { + return -1; + } + return 0; +} +static tb_long_t xm_semver_compare_with_build(semver_t const *left, semver_t const *right) { + tb_long_t result = semver_pcmp(left, right); + // xmake-repo orders build metadata as package revisions + return result ? result : xm_semver_compare_build(&left->build, &right->build); +} +static semver_t const *xm_semvers_find_newest(semvers_t const *versions) { + tb_assert_and_check_return_val(versions && versions->length, tb_null); + + semver_t const *newest = &versions->data[0]; + tb_size_t i = 0; + for (i = 1; i < versions->length; ++i) { + if (xm_semver_compare_with_build(&versions->data[i], newest) > 0) { + newest = &versions->data[i]; + } + } + return newest; +} static tb_bool_t xm_semver_select_from_versions_tags1( lua_State *lua, tb_int_t fromidx, semver_t *semver, semver_range_t const *range, semvers_t *matches) { // clear matches @@ -59,27 +122,24 @@ static tb_bool_t xm_semver_select_from_versions_tags1( // no matches? tb_check_return_val(matches->length, tb_false); - // sort matches - semvers_psort(matches); - // get the newest version - semver_t top = semvers_ppop(matches); + semver_t const *top = xm_semvers_find_newest(matches); lua_createtable(lua, 0, 2); // return results - lua_pushstring(lua, top.raw); + lua_pushstring(lua, top->raw); lua_setfield(lua, -2, "version"); lua_pushstring(lua, fromidx == 2 ? "version" : "tag"); lua_setfield(lua, -2, "source"); - // exit the popped semver - semver_dtor(&top); - return tb_true; } static tb_bool_t xm_semver_select_from_versions_tags2( - lua_State *lua, tb_int_t fromidx, semver_t *semver, tb_char_t const *version_str, tb_size_t version_len) { + lua_State *lua, tb_int_t fromidx, tb_char_t const *version_str, tb_size_t version_len, tb_bool_t is_exact) { + if (is_exact) { + version_str = xm_semver_skip_version_prefix(version_str, &version_len); + } lua_Integer i = 0; luaL_checktype(lua, fromidx, LUA_TTABLE); for (i = lua_objlen(lua, fromidx); i > 0; --i) { @@ -88,8 +148,14 @@ static tb_bool_t xm_semver_select_from_versions_tags2( tb_char_t const *source_str = luaL_checkstring(lua, -1); tb_size_t source_len = tb_strlen(source_str); + tb_size_t source_version_len = source_len; + tb_char_t const *source_version_str = source_str; + // ignore a leading v/= prefix when comparing exact versions + if (is_exact) { + source_version_str = xm_semver_skip_version_prefix(source_str, &source_version_len); + } lua_pop(lua, 1); - if (source_len == version_len && tb_strncmp(source_str, version_str, version_len) == 0) { + if (source_version_len == version_len && tb_strncmp(source_version_str, version_str, version_len) == 0) { lua_createtable(lua, 0, 2); lua_pushlstring(lua, source_str, source_len); lua_setfield(lua, -2, "version"); @@ -147,21 +213,17 @@ static tb_bool_t xm_semver_select_latest_from_versions_tags(lua_State *lua, } tb_check_return_val(matches->length, tb_false); - // sort matches - semvers_psort(matches); - // get the newest match - semver_t top = semvers_ppop(matches); + semver_t const *top = xm_semvers_find_newest(matches); lua_createtable(lua, 0, 2); // return results - lua_pushstring(lua, top.raw); + lua_pushstring(lua, top->raw); lua_setfield(lua, -2, "version"); lua_pushstring(lua, fromidx == 2 ? "version" : "tag"); lua_setfield(lua, -2, "source"); - semver_dtor(&top); return tb_true; } @@ -179,6 +241,8 @@ tb_int_t xm_semver_select(lua_State *lua) { // select version tb_bool_t ok = tb_false; tb_bool_t is_range = tb_false; + tb_bool_t is_exact = tb_false; + tb_bool_t is_exact_with_build = tb_false; tb_char_t const *range_str = tb_null; semver_t semver = { 0 }; semvers_t matches = { 0 }; @@ -193,27 +257,27 @@ tb_int_t xm_semver_select(lua_State *lua) { // parse the version range string is_range = semver_rangen(&range, range_str, range_len) == 0; - if (is_range) { - // attempt to select version from the versions list first - if (xm_semver_select_from_versions_tags1(lua, 2, &semver, &range, &matches)) { + is_exact = xm_semver_is_exact_version(range_str, range_len, &is_exact_with_build); + + // matching order: versions exact -> tags exact -> versions range -> tags range + if (is_exact || !is_range) { + if (xm_semver_select_from_versions_tags2(lua, 2, range_str, range_len, is_exact)) { ok = tb_true; break; } - - // attempt to select version from the tags list - if (xm_semver_select_from_versions_tags1(lua, 3, &semver, &range, &matches)) { + if (xm_semver_select_from_versions_tags2(lua, 3, range_str, range_len, is_exact)) { ok = tb_true; break; } - } else { - // attempt to select version from the versions list first - if (xm_semver_select_from_versions_tags2(lua, 2, &semver, range_str, range_len)) { + } + + // a build-qualified exact version identifies a specific package revision + if (is_range && !is_exact_with_build) { + if (xm_semver_select_from_versions_tags1(lua, 2, &semver, &range, &matches)) { ok = tb_true; break; } - - // attempt to select version from the tags list - if (xm_semver_select_from_versions_tags2(lua, 3, &semver, range_str, range_len)) { + if (xm_semver_select_from_versions_tags1(lua, 3, &semver, &range, &matches)) { ok = tb_true; break; } diff --git a/tests/modules/semver/test.lua b/tests/modules/semver/test.lua index 1ab6dc8aa..57d213f4a 100755 --- a/tests/modules/semver/test.lua +++ b/tests/modules/semver/test.lua @@ -7,6 +7,12 @@ function _check_semver_select(t, results, required_ver, versions, tags, branches t:are_equal(source, results[2]) end +function _check_semver_select_failed(t, required_ver, versions, tags, branches) + t:will_raise(function() + semver.select(required_ver, versions or {}, tags or {}, branches or {}) + end, "unable to select version") +end + -- test select version function test_semver_select(t) @@ -18,15 +24,101 @@ function test_semver_select(t) , "^1.5.0" ,{"1.4.0", "1.5.0", "1.5.1"}) + _check_semver_select(t, {"3.53.0+200", "version"} + , "3.53.0+200" + , {"3.53.0+0", "3.53.0+100", "3.53.0+200"}) + + _check_semver_select_failed(t, "3.53.0+999", {"3.53.0+100"}) + + _check_semver_select(t, {"3.53.0+200", "version"} + , "3.53.0" + , {"3.53.0+0", "3.53.0+100", "3.53.0+200"}) + + _check_semver_select(t, {"3.53.0+200", "version"} + , "3.53.0" + , {"3.53.0+200", "3.53.0+100", "3.53.0+0"}) + + _check_semver_select(t, {"3.53.0+beta", "version"} + , "3.53.0" + , {"3.53.0+alpha", "3.53.0+beta"}) + + _check_semver_select(t, {"3.53.0+beta", "version"} + , "3.53.0" + , {"3.53.0+beta", "3.53.0+alpha"}) + + _check_semver_select(t, {"1.2.3", "version"} + , "1.2.3" + , {"1.2.3+1", "1.2.3"}) + + _check_semver_select(t, {"1.2.3", "version"} + , "=1.2.3" + , {"1.2.3+7", "1.2.3"}) + + _check_semver_select(t, {"1.2.9", "version"} + , "1.2" + , {"1.2", "1.2.9"}) + + _check_semver_select(t, {"1.9.0", "version"} + , "^1.2.3" + , {"^1.2.3", "1.2.3", "1.9.0"}) + + _check_semver_select(t, {"1.2.3", "tag"} + , "1.2.3" + , {"v1.2.3+7"} + , {"1.2.3"}) + + _check_semver_select(t, {"v1.2.3+7", "version"} + , "1.2.3+7" + , {"v1.2.3+8", "v1.2.3+7"}) + + _check_semver_select(t, {"1.2.3+7", "version"} + , "v1.2.3+7" + , {"1.2.3+8", "1.2.3+7"}) + + _check_semver_select(t, {"3.53.0+200", "tag"} + , "3.53.0+200" + , nil + , {"3.53.0+0", "3.53.0+200"}) + + _check_semver_select_failed(t, "3.53.0+999", nil, {"3.53.0+100"}) + + _check_semver_select(t, {"v1.2.3+7", "tag"} + , "=1.2.3+7" + , nil + , {"v1.2.3+7", "v1.2.3+8"}) + + _check_semver_select(t, {"v1.2.3+7", "tag"} + , "1.2.3+7" + , {"1.2.3+8"} + , {"v1.2.3+7"}) + _check_semver_select(t, {"master", "branch"} , "master" , {"1.4.0", "1.5.0", "1.5.1"} , {"v1.2.0", "v1.6.0"} , {"master", "dev"}) + _check_semver_select(t, {"next", "branch"} + , "next" + , nil + , {"vnext"} + , {"next"}) + _check_semver_select(t, {"1.5.1", "version"} , "latest" , {"1.4.0", "1.5.0", "1.5.1"}) + + _check_semver_select(t, {"1.0.0+10", "version"} + , "latest" + , {"1.0.0+9", "1.0.0+10"}) + + _check_semver_select(t, {"1.0.0+rev.10", "version"} + , "latest" + , {"1.0.0+rev.9", "1.0.0+rev.10"}) + + _check_semver_select(t, {"3.53.0+200", "version"} + , "latest" + , {"3.53.0+0", "3.53.0+200", "3.53.0+100"}) end -- select version |
