diff --git a/xmake/modules/package/manager/cargo/install_package.lua b/xmake/modules/package/manager/cargo/install_package.lua index 1dd81a09e5d..71459f86636 100644 --- a/xmake/modules/package/manager/cargo/install_package.lua +++ b/xmake/modules/package/manager/cargo/install_package.lua @@ -47,6 +47,90 @@ function _translate_local_path_in_deps(cargotoml, rootdir) io.writefile(cargotoml, content) end +-- is it a cargo workspace manifest? (it contains a [workspace] or [workspace.xxx] table) +function _is_workspace_manifest(content) + for _, line in ipairs(content:split("\n", {plain = true, strict = true})) do + if line:match("^%s*%[workspace[%].]") then + return true + end + end + return false +end + +-- get the workspace inheritance tables from the workspace root manifest +-- +-- if the given Cargo.toml is a member of a cargo workspace and uses workspace inheritance, +-- e.g. `anyhow.workspace = true`, `version.workspace = true`, `[lints] workspace = true`, +-- we need to inject the `[workspace.package]`/`[workspace.dependencies]`/`[workspace.lints]` +-- tables from the workspace root manifest, otherwise cargo will fail to resolve them, e.g. +-- +-- error inheriting `anyhow` from workspace root manifest's `workspace.dependencies.anyhow` +-- `workspace.dependencies` was not defined +-- +-- @see https://github.com/xmake-io/xmake/issues/7619 +-- https://doc.rust-lang.org/cargo/reference/workspaces.html#the-workspacedependencies-table +function _get_workspace_inherited_tables(cargo_toml) + + -- find the workspace root manifest by walking up + local rootmanifest + local dir = path.directory(path.absolute(cargo_toml)) + while dir and #dir > 0 do + local manifest = path.join(dir, "Cargo.toml") + if os.isfile(manifest) then + local content = io.readfile(manifest) + if content and _is_workspace_manifest(content) then + rootmanifest = manifest + break + end + end + local parentdir = path.directory(dir) + if parentdir == dir then + break + end + dir = parentdir + end + + -- not a workspace member, or the manifest itself is the workspace root (self-contained) + if not rootmanifest or path.absolute(rootmanifest) == path.absolute(cargo_toml) then + return + end + + -- extract all the [workspace.xxx] sub-tables, e.g. [workspace.package]/[workspace.dependencies]/[workspace.lints] + -- we do not copy the bare [workspace] table (members/exclude), otherwise cargo will search for the member crates. + local content = io.readfile(rootmanifest) + if not content then + return + end + local result = {} + local in_subtable = false + for _, line in ipairs(content:split("\n", {plain = true, strict = true})) do + local header = line:match("^%s*%[(.-)%]%s*$") + if header then + in_subtable = header:trim():startswith("workspace.") + if in_subtable then + table.insert(result, line) + end + elseif in_subtable then + table.insert(result, line) + end + end + if #result == 0 then + return + end + + -- translate the local paths in the workspace dependencies, they are relative to the workspace root directory + local rootdir = path.directory(rootmanifest) + local workspace_toml = table.concat(result, "\n") + workspace_toml = workspace_toml:gsub("path%s+=%s+\"(.-)\"", function (localpath) + if not path.is_absolute(localpath) then + localpath = path.absolute(localpath, rootdir) + end + localpath = localpath:gsub("\\", "/") + return "path = \"" .. localpath .. "\"" + end) + return workspace_toml +end + -- install package -- -- e.g. @@ -101,6 +185,13 @@ function main(name, opt) tomlfile:print("") tomlfile:print("[workspace]") tomlfile:print("") + -- inject the workspace inheritance tables if the given Cargo.toml is a workspace member, + -- e.g. `anyhow.workspace = true`. @see https://github.com/xmake-io/xmake/issues/7619 + local workspace_toml = _get_workspace_inherited_tables(configs.cargo_toml) + if workspace_toml then + tomlfile:write(workspace_toml) + tomlfile:print("") + end tomlfile:close() else local tomlfile = io.open(cargotoml, "w")