1
1
//! Get "compiler" args from cargo
2
2
3
3
use crate :: errors:: * ;
4
- use log:: { info, warn} ;
4
+ use cargo_manifest:: { Edition , Manifest , MaybeInherited :: Local } ;
5
+ use log:: { debug, info} ;
5
6
use std:: fs;
6
7
use std:: fs:: File ;
7
8
use std:: io:: prelude:: * ;
@@ -40,45 +41,73 @@ use std::process::Command;
40
41
41
42
#[ derive( Debug ) ]
42
43
pub struct ExternArgs {
43
- suffix_args : Vec < String > ,
44
+ edition : String ,
45
+ crate_name : String ,
46
+ lib_list : Vec < String > ,
47
+ extern_list : Vec < String > ,
44
48
}
45
49
46
50
impl ExternArgs {
47
51
/// simple constructor
48
52
pub fn new ( ) -> Self {
49
53
ExternArgs {
50
- suffix_args : vec ! [ ] ,
54
+ edition : String :: default ( ) ,
55
+ crate_name : String :: default ( ) ,
56
+ lib_list : vec ! [ ] ,
57
+ extern_list : vec ! [ ] ,
51
58
}
52
59
}
53
60
54
61
/// Run a `cargo build` to see what args Cargo is using for library paths and extern crates.
55
- /// Touch a source file to ensure something is compiled and the args will be visible.
56
- ///
57
- /// >>>Future research: see whether `cargo check` can be used instead. It emits the `--extern`s
58
- /// with `.rmeta` instead of `.rlib`, and the compiler can't actually use those
59
- /// when compiling a doctest. But perhaps simply changing the file extension would work?
62
+ /// Touch a source file in the crate to ensure something is compiled and the args will be visible.
63
+
60
64
pub fn load ( & mut self , proj_root : & Path ) -> Result < & Self > {
65
+ // find Cargo.toml and determine the package name and lib or bin source file.
66
+ let cargo_path = proj_root. join ( "Cargo.toml" ) ;
67
+ let mut manifest = Manifest :: from_path ( & cargo_path) ?;
68
+ manifest. complete_from_path ( proj_root) ?; // try real hard to determine bin or lib
69
+ let package = manifest
70
+ . package
71
+ . expect ( "doctest Cargo.toml must include a [package] section" ) ;
72
+
73
+ self . crate_name = package. name . replace ( '-' , "_" ) ; // maybe cargo shouldn't allow packages to include non-identifier characters?
74
+ // in any case, this won't work when default crate doesn't have package name (which I'm sure cargo allows somehow or another)
75
+ self . edition = if let Some ( Local ( edition) ) = package. edition {
76
+ my_display_edition ( edition)
77
+ } else {
78
+ "2015" . to_owned ( ) // and good luck to you, sir!
79
+ } ;
80
+
81
+ debug ! (
82
+ "parsed from manifest: name: {}, edition: {}" ,
83
+ self . crate_name,
84
+ format!( "{:?}" , self . edition)
85
+ ) ;
86
+
61
87
// touch (change) a file in the project to force check to do something
88
+ // I haven't figured out how to determine bin or lib source file from cargo, fall back on heuristics here.
62
89
63
- for fname in [ "lib.rs" , "main.rs" ] {
64
- let try_path: PathBuf = [ & proj_root. to_string_lossy ( ) , "src" , fname]
65
- . iter ( )
66
- . collect ( ) ;
90
+ for fname in [ "main.rs" , "lib.rs" ] {
91
+ let try_path: PathBuf = proj_root. join ( "src" ) . join ( fname) ;
67
92
if try_path. exists ( ) {
68
93
touch ( & try_path) ?;
69
- self . run_cargo ( proj_root) ?;
94
+ self . run_cargo ( proj_root, & cargo_path ) ?;
70
95
return Ok ( self ) ;
71
96
// file should be closed when f goes out of scope at bottom of this loop
72
97
}
73
98
}
74
- bail ! ( "Couldn't find source target in project {:?}" , proj_root)
99
+ bail ! ( "Couldn't find lib or bin source in project {:?}" , proj_root)
75
100
}
76
101
77
- fn run_cargo ( & mut self , proj_root : & Path ) -> Result < & Self > {
102
+ fn run_cargo ( & mut self , proj_root : & Path , manifest_path : & Path ) -> Result < & Self > {
78
103
let mut cmd = Command :: new ( "cargo" ) ;
79
- cmd. current_dir ( & proj_root) . arg ( "build" ) . arg ( "--verbose" ) ;
80
-
104
+ cmd. current_dir ( & proj_root)
105
+ . arg ( "build" )
106
+ . arg ( "--verbose" )
107
+ . arg ( "--manifest-path" )
108
+ . arg ( manifest_path) ;
81
109
info ! ( "running {:?}" , cmd) ;
110
+
82
111
let output = cmd. output ( ) ?;
83
112
84
113
if !output. status . success ( ) {
@@ -90,63 +119,107 @@ impl ExternArgs {
90
119
) ;
91
120
}
92
121
122
+ //ultimatedebug std::fs::write(proj_root.join("mdbook_cargo_out.txt"), &output.stderr)?;
123
+
93
124
let cmd_resp: & str = std:: str:: from_utf8 ( & output. stderr ) ?;
94
- self . parse_response ( & cmd_resp) ?;
125
+ self . parse_response ( & self . crate_name . clone ( ) , & cmd_resp) ?;
95
126
96
127
Ok ( self )
97
128
}
98
129
99
130
/// Parse response stdout+stderr response from `cargo build`
100
- /// into arguments we can use to invoke rustdoc.
101
- /// Stop at first line that traces a compiler invocation .
131
+ /// into arguments we can use to invoke rustdoc (--edition --extern and -L) .
132
+ /// The response may contain multiple builds, scan for the one that corresponds to the doctest crate .
102
133
///
103
- /// >>> This parser is broken, doesn't handle arg values with embedded spaces (single quoted).
134
+ /// > This parser is broken, doesn't handle arg values with embedded spaces (single quoted).
104
135
/// Fortunately, the args we care about (so far) don't have those kinds of values.
105
- pub fn parse_response ( & mut self , buf : & str ) -> Result < ( ) > {
136
+ pub fn parse_response ( & mut self , my_crate : & str , buf : & str ) -> Result < ( ) > {
137
+ let mut builds_ignored = 0 ;
138
+
139
+ let my_cn_arg = format ! ( " --crate-name {}" , my_crate) ;
106
140
for l in buf. lines ( ) {
107
141
if let Some ( _i) = l. find ( " Running " ) {
108
- let args_seg: & str = l. split ( '`' ) . skip ( 1 ) . take ( 1 ) . collect :: < Vec < _ > > ( ) [ 0 ] ; // sadly, cargo decorates string with backticks
109
- let mut arg_iter = args_seg. split_whitespace ( ) ;
110
-
111
- while let Some ( arg) = arg_iter. next ( ) {
112
- match arg {
113
- "-L" | "--library-path" => {
114
- self . suffix_args . push ( arg. to_owned ( ) ) ;
115
- self . suffix_args
116
- . push ( arg_iter. next ( ) . unwrap_or ( "" ) . to_owned ( ) ) ;
142
+ if let Some ( _cn_pos) = l. find ( & my_cn_arg) {
143
+ let args_seg: & str = l. split ( '`' ) . skip ( 1 ) . take ( 1 ) . collect :: < Vec < _ > > ( ) [ 0 ] ; // sadly, cargo decorates string with backticks
144
+ let mut arg_iter = args_seg. split_whitespace ( ) ;
145
+
146
+ while let Some ( arg) = arg_iter. next ( ) {
147
+ match arg {
148
+ "-L" | "--library-path" => {
149
+ self . lib_list
150
+ . push ( arg_iter. next ( ) . unwrap_or_default ( ) . to_owned ( ) ) ;
151
+ }
152
+
153
+ "--extern" => {
154
+ let mut dep_arg = arg_iter. next ( ) . unwrap_or_default ( ) . to_owned ( ) ;
155
+
156
+ // sometimes, build references the.rmeta even though our doctests will require .rlib
157
+ // so convert the argument and hope for the best.
158
+ // if .rlib is not there when the doctest runs, it will complain.
159
+ if dep_arg. ends_with ( ".rmeta" ) {
160
+ debug ! (
161
+ "Build referenced {}, converted to .rlib hoping that actual file will be there in time." ,
162
+ dep_arg) ;
163
+ dep_arg = dep_arg. replace ( ".rmeta" , ".rlib" ) ;
164
+ }
165
+ self . extern_list . push ( dep_arg) ;
166
+ }
167
+
168
+ "--crate-name" => {
169
+ self . crate_name = arg_iter. next ( ) . unwrap_or_default ( ) . to_owned ( ) ;
170
+ }
171
+
172
+ _ => {
173
+ if let Some ( ( kw, val) ) = arg. split_once ( '=' ) {
174
+ if kw == "--edition" {
175
+ self . edition = val. to_owned ( ) ;
176
+ }
177
+ }
178
+ }
117
179
}
118
- "--extern" => {
119
- // needs a hack to force reference to rlib over rmeta
120
- self . suffix_args . push ( arg. to_owned ( ) ) ;
121
- self . suffix_args . push (
122
- arg_iter
123
- . next ( )
124
- . unwrap_or ( "" )
125
- . replace ( ".rmeta" , ".rlib" )
126
- . to_owned ( ) ,
127
- ) ;
128
- }
129
- _ => { }
130
180
}
181
+ } else {
182
+ builds_ignored += 1 ;
131
183
}
132
-
133
- return Ok ( ( ) ) ;
134
184
} ;
135
185
}
136
186
137
- if self . suffix_args . len ( ) < 1 {
138
- warn ! ( "Couldn't extract --extern args from Cargo, is current directory == cargo project root?" ) ;
187
+ if self . extern_list . len ( ) == 0 || self . lib_list . len ( ) == 0 {
188
+ bail ! ( "Couldn't extract -L or --extern args from Cargo, is current directory == cargo project root?" ) ;
139
189
}
140
190
191
+ debug ! (
192
+ "Ignored {} other builds performed in this run" ,
193
+ builds_ignored
194
+ ) ;
195
+
141
196
Ok ( ( ) )
142
197
}
143
198
144
- /// get a list of (- L and --extern) args used to invoke rustdoc .
199
+ /// provide the parsed external args used to invoke rustdoc (--edition, - L and --extern).
145
200
pub fn get_args ( & self ) -> Vec < String > {
146
- self . suffix_args . clone ( )
201
+ let mut ret_val: Vec < String > = vec ! [ "--edition" . to_owned( ) , self . edition. clone( ) ] ;
202
+ for i in & self . lib_list {
203
+ ret_val. push ( "-L" . to_owned ( ) ) ;
204
+ ret_val. push ( i. clone ( ) ) ;
205
+ }
206
+ for j in & self . extern_list {
207
+ ret_val. push ( "--extern" . to_owned ( ) ) ;
208
+ ret_val. push ( j. clone ( ) ) ;
209
+ }
210
+ ret_val
147
211
}
148
212
}
149
213
214
+ fn my_display_edition ( edition : Edition ) -> String {
215
+ match edition {
216
+ Edition :: E2015 => "2015" ,
217
+ Edition :: E2018 => "2018" ,
218
+ Edition :: E2021 => "2021" ,
219
+ Edition :: E2024 => "2024" ,
220
+ }
221
+ . to_owned ( )
222
+ }
150
223
// Private "touch" function to update file modification time without changing content.
151
224
// needed because [std::fs::set_modified] is unstable in rust 1.74,
152
225
// which is currently the MSRV for mdBook. It is available in rust 1.76 onward.
@@ -196,7 +269,7 @@ mod test {
196
269
"### ;
197
270
198
271
let mut ea = ExternArgs :: new ( ) ;
199
- ea. parse_response ( & test_str) ?;
272
+ ea. parse_response ( & test_str, "leptos_book" ) ?;
200
273
201
274
let args = ea. get_args ( ) ;
202
275
assert_eq ! ( 18 , args. len( ) ) ;
0 commit comments