@@ -15,19 +15,72 @@ pub fn expand_manifest(
15
15
path : & std:: path:: Path ,
16
16
config : & Config ,
17
17
) -> CargoResult < String > {
18
- let comment = extract_comment ( content) ?. unwrap_or_default ( ) ;
19
- let manifest = match extract_manifest ( & comment) ? {
20
- Some ( manifest) => Some ( manifest) ,
21
- None => {
22
- tracing:: trace!( "failed to extract manifest" ) ;
23
- None
18
+ let source = split_source ( content) ?;
19
+ if let Some ( frontmatter) = source. frontmatter {
20
+ match source. info {
21
+ Some ( "cargo" ) => { }
22
+ None => {
23
+ anyhow:: bail!( "frontmatter is missing an infostring; specify `cargo` for embedding a manifest" ) ;
24
+ }
25
+ Some ( other) => {
26
+ if let Some ( remainder) = other. strip_prefix ( "cargo," ) {
27
+ anyhow:: bail!( "cargo does not support frontmatter infostring attributes like `{remainder}` at this time" )
28
+ } else {
29
+ anyhow:: bail!( "frontmatter infostring `{other}` is unsupported by cargo; specify `cargo` for embedding a manifest" )
30
+ }
31
+ }
32
+ }
33
+
34
+ // HACK: until rustc has native support for this syntax, we have to remove it from the
35
+ // source file
36
+ use std:: fmt:: Write as _;
37
+ let hash = crate :: util:: hex:: short_hash ( & path. to_string_lossy ( ) ) ;
38
+ let mut rel_path = std:: path:: PathBuf :: new ( ) ;
39
+ rel_path. push ( "target" ) ;
40
+ rel_path. push ( & hash[ 0 ..2 ] ) ;
41
+ rel_path. push ( & hash[ 2 ..] ) ;
42
+ let target_dir = config. home ( ) . join ( rel_path) ;
43
+ let hacked_path = target_dir
44
+ . join (
45
+ path. file_name ( )
46
+ . expect ( "always a name for embedded manifests" ) ,
47
+ )
48
+ . into_path_unlocked ( ) ;
49
+ let mut hacked_source = String :: new ( ) ;
50
+ if let Some ( shebang) = source. shebang {
51
+ writeln ! ( hacked_source, "{shebang}" ) ?;
52
+ }
53
+ writeln ! ( hacked_source) ?; // open
54
+ for _ in 0 ..frontmatter. lines ( ) . count ( ) {
55
+ writeln ! ( hacked_source) ?;
24
56
}
57
+ writeln ! ( hacked_source) ?; // close
58
+ writeln ! ( hacked_source, "{}" , source. content) ?;
59
+ if let Some ( parent) = hacked_path. parent ( ) {
60
+ cargo_util:: paths:: create_dir_all ( parent) ?;
61
+ }
62
+ cargo_util:: paths:: write_if_changed ( & hacked_path, hacked_source) ?;
63
+
64
+ let manifest = expand_manifest_ ( & frontmatter, & hacked_path, config)
65
+ . with_context ( || format ! ( "failed to parse manifest at {}" , path. display( ) ) ) ?;
66
+ let manifest = toml:: to_string_pretty ( & manifest) ?;
67
+ Ok ( manifest)
68
+ } else {
69
+ // Legacy doc-comment support; here only for transitional purposes
70
+ let comment = extract_comment ( content) ?. unwrap_or_default ( ) ;
71
+ let manifest = match extract_manifest ( & comment) ? {
72
+ Some ( manifest) => Some ( manifest) ,
73
+ None => {
74
+ tracing:: trace!( "failed to extract manifest" ) ;
75
+ None
76
+ }
77
+ }
78
+ . unwrap_or_default ( ) ;
79
+ let manifest = expand_manifest_ ( & manifest, path, config)
80
+ . with_context ( || format ! ( "failed to parse manifest at {}" , path. display( ) ) ) ?;
81
+ let manifest = toml:: to_string_pretty ( & manifest) ?;
82
+ Ok ( manifest)
25
83
}
26
- . unwrap_or_default ( ) ;
27
- let manifest = expand_manifest_ ( & manifest, path, config)
28
- . with_context ( || format ! ( "failed to parse manifest at {}" , path. display( ) ) ) ?;
29
- let manifest = toml:: to_string_pretty ( & manifest) ?;
30
- Ok ( manifest)
31
84
}
32
85
33
86
fn expand_manifest_ (
@@ -59,11 +112,8 @@ fn expand_manifest_(
59
112
anyhow:: bail!( "`package.{key}` is not allowed in embedded manifests" )
60
113
}
61
114
}
62
- let bin_path = path
63
- . file_name ( )
64
- . ok_or_else ( || anyhow:: format_err!( "no file name" ) ) ?
65
- . to_string_lossy ( )
66
- . into_owned ( ) ;
115
+ // HACK: Using an absolute path while `hacked_path` is in use
116
+ let bin_path = path. to_string_lossy ( ) . into_owned ( ) ;
67
117
let file_stem = path
68
118
. file_stem ( )
69
119
. ok_or_else ( || anyhow:: format_err!( "no file name" ) ) ?
@@ -150,6 +200,80 @@ fn sanitize_name(name: &str) -> String {
150
200
name
151
201
}
152
202
203
+ struct Source < ' s > {
204
+ shebang : Option < & ' s str > ,
205
+ info : Option < & ' s str > ,
206
+ frontmatter : Option < & ' s str > ,
207
+ content : & ' s str ,
208
+ }
209
+
210
+ fn split_source ( input : & str ) -> CargoResult < Source < ' _ > > {
211
+ let mut source = Source {
212
+ shebang : None ,
213
+ info : None ,
214
+ frontmatter : None ,
215
+ content : input,
216
+ } ;
217
+
218
+ // See rust-lang/rust's compiler/rustc_lexer/src/lib.rs's `strip_shebang`
219
+ // Shebang must start with `#!` literally, without any preceding whitespace.
220
+ // For simplicity we consider any line starting with `#!` a shebang,
221
+ // regardless of restrictions put on shebangs by specific platforms.
222
+ if let Some ( rest) = source. content . strip_prefix ( "#!" ) {
223
+ // Ok, this is a shebang but if the next non-whitespace token is `[`,
224
+ // then it may be valid Rust code, so consider it Rust code.
225
+ if rest. trim_start ( ) . starts_with ( '[' ) {
226
+ return Ok ( source) ;
227
+ }
228
+
229
+ // No other choice than to consider this a shebang.
230
+ let ( shebang, content) = source
231
+ . content
232
+ . split_once ( '\n' )
233
+ . unwrap_or ( ( source. content , "" ) ) ;
234
+ source. shebang = Some ( shebang) ;
235
+ source. content = content;
236
+ }
237
+
238
+ let tick_end = source
239
+ . content
240
+ . char_indices ( )
241
+ . find_map ( |( i, c) | ( c != '`' ) . then_some ( i) )
242
+ . unwrap_or ( source. content . len ( ) ) ;
243
+ let ( fence_pattern, rest) = match tick_end {
244
+ 0 => {
245
+ return Ok ( source) ;
246
+ }
247
+ 1 | 2 => {
248
+ anyhow:: bail!( "found {tick_end} backticks in rust frontmatter, expected at least 3" )
249
+ }
250
+ _ => source. content . split_at ( tick_end) ,
251
+ } ;
252
+ let ( info, content) = rest. split_once ( "\n " ) . unwrap_or ( ( rest, "" ) ) ;
253
+ if !info. is_empty ( ) {
254
+ source. info = Some ( info. trim_end ( ) ) ;
255
+ }
256
+ source. content = content;
257
+
258
+ let Some ( ( frontmatter, content) ) = source. content . split_once ( fence_pattern) else {
259
+ anyhow:: bail!( "no closing `{fence_pattern}` found for frontmatter" ) ;
260
+ } ;
261
+ source. frontmatter = Some ( frontmatter) ;
262
+ source. content = content;
263
+
264
+ let ( line, content) = source
265
+ . content
266
+ . split_once ( "\n " )
267
+ . unwrap_or ( ( source. content , "" ) ) ;
268
+ let line = line. trim ( ) ;
269
+ if !line. is_empty ( ) {
270
+ anyhow:: bail!( "unexpected trailing content on closing fence: `{line}`" ) ;
271
+ }
272
+ source. content = content;
273
+
274
+ Ok ( source)
275
+ }
276
+
153
277
/// Locates a "code block manifest" in Rust source.
154
278
fn extract_comment ( input : & str ) -> CargoResult < Option < String > > {
155
279
let mut doc_fragments = Vec :: new ( ) ;
@@ -487,7 +611,7 @@ mod test_expand {
487
611
snapbox:: assert_eq (
488
612
r#"[[bin]]
489
613
name = "test-"
490
- path = "test.rs"
614
+ path = "/home/me/ test.rs"
491
615
492
616
[package]
493
617
autobenches = false
@@ -514,7 +638,7 @@ strip = true
514
638
snapbox:: assert_eq (
515
639
r#"[[bin]]
516
640
name = "test-"
517
- path = "test.rs"
641
+ path = "/home/me/ test.rs"
518
642
519
643
[dependencies]
520
644
time = "0.1.25"
0 commit comments