@@ -171,43 +171,50 @@ fn parse_pyproject(content: &str) -> Option<String> {
171171/// them:
172172///
173173/// 1. PEP 621 `[project]` `name` + `version` (newer projects favor it).
174- /// 2. `[project].name` with `version` listed in `[project].dynamic`: Poetry
175- /// 2 then takes the version from `[tool.poetry].version`.
176- /// 3. The legacy tables that hold both keys: Poetry's `[tool.poetry]`,
177- /// then PDM 0.x's `[tool.pdm]` (PDM 1.x migrates it to `[project]`).
178- ///
179- /// `[tool.poetry].version` counts only when Poetry builds the project and
180- /// poetry-dynamic-versioning does not replace it at build time: under
181- /// another backend (hatch-vcs, setuptools-scm) it is a leftover, and under
182- /// the plugin it is a placeholder such as `0.0.0`.
174+ /// 2. When Poetry builds the project, poetry-core's own merge: the name is
175+ /// `[project].name` or else `[tool.poetry].name`, and the version is
176+ /// `[project].version` or else `[tool.poetry].version`. This covers
177+ /// Poetry 2's dynamic version and the legacy `[tool.poetry]` layout.
178+ /// 3. PDM 0.x's legacy `[tool.pdm]` table (PDM 1.x migrates it to
179+ /// `[project]`).
183180fn pyproject_name_version ( doc : & toml_edit:: DocumentMut ) -> Option < ( String , String ) > {
184181 let project = toml_table ( doc, "project" ) ;
185182 let project_str = |key| project. and_then ( |t| table_str ( t, key) ) ;
186- if let Some ( name) = project_str ( "name" ) {
187- if let Some ( version) = project_str ( "version" ) {
188- return Some ( ( name. to_string ( ) , version. to_string ( ) ) ) ;
189- }
190- let version_is_dynamic = project
191- . and_then ( |t| t. get ( "dynamic" ) )
192- . and_then ( |d| d. as_array ( ) )
193- . is_some_and ( |a| a. iter ( ) . any ( |v| v. as_str ( ) == Some ( "version" ) ) ) ;
194- if version_is_dynamic && poetry_version_is_static ( doc) {
195- if let Some ( version) = toml_str ( doc, "tool.poetry" , "version" ) {
196- return Some ( ( name. to_string ( ) , version. to_string ( ) ) ) ;
183+ let ( project_name, project_version) = ( project_str ( "name" ) , project_str ( "version" ) ) ;
184+ if let ( Some ( name) , Some ( version) ) = ( project_name, project_version) {
185+ return Some ( ( name. to_string ( ) , version. to_string ( ) ) ) ;
186+ }
187+ if poetry_version_is_static ( doc) {
188+ let poetry_name = toml_str ( doc, "tool.poetry" , "name" ) ;
189+ // Poetry 2 builds `[project].name`, Poetry 1.x ignores `[project]`
190+ // and builds `[tool.poetry].name`: when they differ, the product
191+ // depends on the Poetry line, so name none rather than guess.
192+ if let ( Some ( a) , Some ( b) ) = ( project_name, poetry_name) {
193+ if a != b {
194+ return None ;
197195 }
198196 }
197+ let name = project_name. or ( poetry_name) ;
198+ let version = project_version. or_else ( || toml_str ( doc, "tool.poetry" , "version" ) ) ;
199+ if let ( Some ( name) , Some ( version) ) = ( name, version) {
200+ return Some ( ( name. to_string ( ) , version. to_string ( ) ) ) ;
201+ }
199202 }
200- let poetry = toml_name_version ( doc, "tool.poetry" ) . filter ( |_| poetry_version_is_static ( doc) ) ;
201- poetry. or_else ( || toml_name_version ( doc, "tool.pdm" ) )
203+ toml_name_version ( doc, "tool.pdm" )
202204}
203205
204- /// Whether `[tool.poetry].version` is the version the build produces: no
205- /// non-Poetry `[build-system].build-backend` (Poetry's own projects may
206- /// omit the table), and poetry-dynamic-versioning not enabled.
206+ /// Whether Poetry builds the project from its static `[tool.poetry]`
207+ /// metadata: no non-Poetry `[build-system].build-backend` (Poetry's own
208+ /// projects may omit the table), and poetry-dynamic-versioning not
209+ /// enabled. Under another backend (hatch-vcs, setuptools-scm) the table is
210+ /// a leftover, and under the enabled plugin its version is a placeholder
211+ /// such as `0.0.0`. The plugin's backend re-exports poetry-core's and
212+ /// changes nothing until `enable = true`.
207213fn poetry_version_is_static ( doc : & toml_edit:: DocumentMut ) -> bool {
208214 // `poetry.core.masonry.api`, or `poetry.masonry.api` before poetry-core.
209- let poetry_backend = toml_str ( doc, "build-system" , "build-backend" )
210- . is_none_or ( |backend| backend. starts_with ( "poetry." ) ) ;
215+ let poetry_backend = toml_str ( doc, "build-system" , "build-backend" ) . is_none_or ( |backend| {
216+ backend. starts_with ( "poetry." ) || backend == "poetry_dynamic_versioning.backend"
217+ } ) ;
211218 let dynamic_versioning = toml_table ( doc, "tool.poetry-dynamic-versioning" )
212219 . and_then ( |t| t. get ( "enable" ) )
213220 . and_then ( |e| e. as_bool ( ) )
@@ -1294,12 +1301,40 @@ mod tests {
12941301 ) ;
12951302 }
12961303
1297- /// Without `version` in `dynamic`, Poetry does not take the
1298- /// version from `[tool.poetry]`, and neither does detection.
1304+ /// poetry-core takes `[tool.poetry].version` when `[project]` has
1305+ /// none, whether or not `dynamic` lists it (poetry-core 2.2
1306+ /// `Factory.create_poetry`): this builds `demo 0.3.0`.
1307+ #[ test]
1308+ fn pyproject_tool_poetry_version_without_dynamic ( ) {
1309+ let content = "[project]\n name = \" demo\" \n \n [tool.poetry]\n version = \" 0.3.0\" \n \n [build-system]\n build-backend = \" poetry.core.masonry.api\" \n " ;
1310+ assert_eq ! (
1311+ parse_pyproject( content) . as_deref( ) ,
1312+ Some ( "pkg:pypi/demo@0.3.0" )
1313+ ) ;
1314+ }
1315+
1316+ /// `[project].name` and a different `[tool.poetry].name`: Poetry 2
1317+ /// builds `new`, Poetry 1.x builds `old`, so no product is named
1318+ /// rather than a guess.
12991319 #[ test]
1300- fn pyproject_tool_poetry_version_needs_dynamic ( ) {
1301- let content = "[project]\n name = \" demo\" \n dynamic = [\" readme\" ]\n \n [tool.poetry]\n version = \" 0.3.0\" \n " ;
1320+ fn pyproject_conflicting_poetry_names_is_none ( ) {
1321+ let content =
1322+ "[project]\n name = \" new\" \n \n [tool.poetry]\n name = \" old\" \n version = \" 1.0\" \n " ;
13021323 assert ! ( parse_pyproject( content) . is_none( ) ) ;
1324+ let same =
1325+ "[project]\n name = \" same\" \n \n [tool.poetry]\n name = \" same\" \n version = \" 1.0\" \n " ;
1326+ assert_eq ! ( parse_pyproject( same) . as_deref( ) , Some ( "pkg:pypi/same@1.0" ) ) ;
1327+ }
1328+
1329+ /// The plugin's backend re-exports poetry-core's and patches nothing
1330+ /// until `enable = true`, so the static version is what it builds.
1331+ #[ test]
1332+ fn pyproject_dynamic_versioning_backend_disabled_keeps_static_version ( ) {
1333+ let content = "[tool.poetry]\n name = \" demo\" \n version = \" 1.2.3\" \n \n [build-system]\n requires = [\" poetry-core\" , \" poetry-dynamic-versioning\" ]\n build-backend = \" poetry_dynamic_versioning.backend\" \n " ;
1334+ assert_eq ! (
1335+ parse_pyproject( content) . as_deref( ) ,
1336+ Some ( "pkg:pypi/demo@1.2.3" )
1337+ ) ;
13031338 }
13041339
13051340 /// A dynamic version with no `[tool.poetry].version` (setuptools-scm,
0 commit comments