summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-08-10 19:51:44 +0800
committerGitHub <[email protected]>2026-08-10 19:51:44 +0800
commitcd41cf60b38492309b32ac7b1238ef8669a8e3de (patch)
tree4e56bfe9b842174c629ed88acb28d1efe1a7121b
parentced7f2ae519bc9eca92bcf4f177fe177ddd0e747 (diff)
parent30c7ad380873ec4c2e52de15ee8e236dfcd38948 (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.c120
-rwxr-xr-xtests/modules/semver/test.lua92
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