@@ -44,9 +44,28 @@ fn manifest_version(crate_name: &str) -> &'static str {
4444
4545/// Find the version requirement declared for `crate_name` in `manifest`, or
4646/// `None` when it is not declared.
47+ ///
48+ /// Two layouts have to work: the one-line form the repository manifest uses,
49+ /// and the `[dependencies.name]` table that `cargo package` rewrites it into.
50+ /// The published binary reads the second.
4751fn parse_manifest_version < ' a > ( manifest : & ' a str , crate_name : & str ) -> Option < & ' a str > {
52+ let mut inside_table_for_crate = false ;
4853 for line in manifest. lines ( ) {
4954 let line = line. trim ( ) ;
55+
56+ if let Some ( header) = line. strip_prefix ( '[' ) . and_then ( |rest| return rest. strip_suffix ( ']' ) ) {
57+ inside_table_for_crate = is_dependency_table_for ( header, crate_name) ;
58+ continue ;
59+ }
60+
61+ if inside_table_for_crate
62+ && let Some ( rest) = line. strip_prefix ( "version" )
63+ && let Some ( value) = rest. trim_start ( ) . strip_prefix ( '=' )
64+ && let Some ( version) = first_quoted ( value)
65+ {
66+ return Some ( version) ;
67+ }
68+
5069 let Some ( rest) = line. strip_prefix ( crate_name) else {
5170 continue ;
5271 } ;
@@ -69,16 +88,32 @@ fn parse_manifest_version<'a>(manifest: &'a str, crate_name: &str) -> Option<&'a
6988 } ,
7089 None => value,
7190 } ;
72- if let Some ( open) = scan. find ( '"' ) {
73- let after = & scan[ open + 1 ..] ;
74- if let Some ( close) = after. find ( '"' ) {
75- return Some ( & after[ ..close] ) ;
76- }
91+ if let Some ( version) = first_quoted ( scan) {
92+ return Some ( version) ;
7793 }
7894 }
7995 return None ;
8096}
8197
98+ /// Whether `header` names the dependency table for `crate_name`, in any of the
99+ /// three dependency scopes `cargo package` can write.
100+ fn is_dependency_table_for ( header : & str , crate_name : & str ) -> bool {
101+ for scope in [ "dependencies." , "dev-dependencies." , "build-dependencies." ] {
102+ if let Some ( name) = header. strip_prefix ( scope)
103+ && name == crate_name
104+ {
105+ return true ;
106+ }
107+ }
108+ return false ;
109+ }
110+
111+ /// The text between the first pair of double quotes in `text`.
112+ fn first_quoted ( text : & str ) -> Option < & str > {
113+ let after = text. split_once ( '"' ) ?. 1 ;
114+ return after. split_once ( '"' ) . map ( |( value, _) | return value) ;
115+ }
116+
82117/// A crate the generated code references, with the version requirement and Cargo
83118/// features a consumer must declare in `Cargo.toml`.
84119#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -244,6 +279,63 @@ mod tests {
244279 assert_eq ! ( parse_manifest_version( manifest, "axum" ) , Some ( "0.8.9" ) ) ;
245280 }
246281
282+ #[ test]
283+ fn a_version_is_found_in_the_layout_cargo_publishes ( ) {
284+ // The shape `cargo package` writes: a table per dependency, rather than
285+ // the one line per dependency the repository manifest uses. The crates
286+ // are invented, so no dependency bump reaches this.
287+ let manifest = r#"
288+ [package]
289+ name = "specimen"
290+ version = "9.9.9"
291+
292+ [dependencies.plain]
293+ version = "1.2.3"
294+
295+ [dependencies.with-features]
296+ version = "4.5.6"
297+ features = ["one", "two"]
298+
299+ [dependencies.multi-line-features]
300+ version = "7.8.9"
301+ features = [
302+ "one",
303+ "two",
304+ ]
305+
306+ [dependencies.prefix]
307+ version = "0.1.0"
308+
309+ [dependencies.prefix_extended]
310+ version = "0.2.0"
311+
312+ [dev-dependencies.only-for-tests]
313+ version = "5.0.0"
314+
315+ [build-dependencies.only-for-build]
316+ version = "6.0.0"
317+ "# ;
318+
319+ for ( crate_name, expected) in [
320+ ( "plain" , Some ( "1.2.3" ) ) ,
321+ ( "with-features" , Some ( "4.5.6" ) ) ,
322+ ( "multi-line-features" , Some ( "7.8.9" ) ) ,
323+ ( "prefix" , Some ( "0.1.0" ) ) ,
324+ ( "prefix_extended" , Some ( "0.2.0" ) ) ,
325+ ( "only-for-tests" , Some ( "5.0.0" ) ) ,
326+ ( "only-for-build" , Some ( "6.0.0" ) ) ,
327+ ( "absent" , None ) ,
328+ // The package table carries a `version` of its own.
329+ ( "specimen" , None ) ,
330+ ] {
331+ assert_eq ! (
332+ parse_manifest_version( manifest, crate_name) ,
333+ expected,
334+ "the published layout should report `{crate_name}` as {expected:?}"
335+ ) ;
336+ }
337+ }
338+
247339 #[ test]
248340 fn toml_renders_short_and_table_forms ( ) {
249341 assert_eq ! (
0 commit comments