-
Notifications
You must be signed in to change notification settings - Fork 6
/
build.rs
742 lines (681 loc) · 31 KB
/
build.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
extern crate bindgen;
extern crate shlex;
use bindgen::builder;
use std::env;
use std::fmt::Write;
use std::path::PathBuf;
use serde_json::json;
fn main() {
let cc;
let mut cflags;
#[cfg(not(feature = "riot-rs"))]
if env::var("BUILDING_RIOT_RS").is_ok() {
println!("");
println!("ERROR: riot-sys seems to be built for RIOT-rs (BUILDING_RIOT_RS is set). Please enable the 'riot-rs' feature.");
println!(
"To do this, make the main application crate depend on `riot-sys` with feature `riot-rs`."
);
println!("");
std::process::exit(1);
}
#[cfg(not(feature = "riot-rs"))]
let compile_commands_json = "RIOT_COMPILE_COMMANDS_JSON";
#[cfg(feature = "riot-rs")]
let compile_commands_json = "DEP_RIOT_BUILD_COMPILE_COMMANDS_JSON";
println!("cargo:rerun-if-env-changed=BUILDING_RIOT_RS");
println!("cargo:rerun-if-env-changed=RIOT_CC");
println!("cargo:rerun-if-env-changed=RIOT_CFLAGS");
println!("cargo:rerun-if-env-changed={}", &compile_commands_json);
if let Ok(commands_json) = env::var(compile_commands_json) {
println!("cargo:rerun-if-changed={}", commands_json);
let commands_file = std::fs::File::open(&commands_json)
.expect(&format!("Failed to open {}", &commands_json));
#[derive(Debug, serde::Deserialize)]
struct Entry {
arguments: Vec<String>,
}
let parsed: Vec<Entry> = serde_json::from_reader(commands_file)
.expect(&format!("Failed to parse {}", &compile_commands_json));
// We need to find a consensus list -- otherwise single modules like stdio_uart that
// defines anything odd for its own purpose can throw things off. (It's not like the actual
// ABI compatibility should suffer from them, for any flags like enum packing need to be
// the same systemwide anyway for things to to go very wrong) -- but at any rate, finding
// some consensus is to some extent necessary here).
//
// This is relatively brittle, but still better than the previous approach of just taking
// the first entry.
//
// A good long-term solution might be to take CFLAGS as the build system produces them, but
// pass them through the LLVMization process of create_compile_commands without actually
// turning them into compile commands.
let mut consensus_cc: Option<&str> = None;
let mut consensus_cflag_groups: Option<Vec<Vec<&str>>> = None;
for entry in parsed.iter() {
if let Some(consensus_cc) = consensus_cc.as_ref() {
assert!(consensus_cc == &entry.arguments[0])
} else {
consensus_cc = Some(&entry.arguments[0]);
}
let arg_iter = entry.arguments[1..]
.iter()
.map(|s| s.as_str())
// Anything after -c is not CFLAGS but concrete input/output stuff.
.take_while(|&s| s != "-c" && s != "-MQ");
// Heuristically grouping them to drop different arguments as whole group
let mut cflag_groups = vec![];
for mut arg in arg_iter {
if arg.starts_with("-I") {
// -I arguments are given inconsistently with and without trailing slashes;
// removing them keeps them from being pruned from the consensus set
arg = arg.trim_end_matches('/');
}
if arg.starts_with('-') {
cflag_groups.push(vec![arg]);
} else {
cflag_groups
.last_mut()
.expect("CFLAG options all start with a dash")
.push(arg);
}
}
if let Some(consensus_cflag_groups) = consensus_cflag_groups.as_mut() {
if &cflag_groups != consensus_cflag_groups {
// consensus is in a good ordering, so we'll just strip it down
*consensus_cflag_groups = consensus_cflag_groups
.drain(..)
.filter(|i| {
let mut keep = cflag_groups.contains(i);
// USEMODULE_INCLUDES are sometimes not in all of the entries; see note
// on brittleness above.
keep |= i[0].starts_with("-I");
// Left as multiple lines to ease hooking in with debug statements when
// something goes wrong again...
keep
})
.collect();
// Hot-fixing the merging algorithm to even work when an (always to be kept) -I
// is not in the initial set
for group in cflag_groups.drain(..) {
if group[0].starts_with("-I") {
if !consensus_cflag_groups.contains(&group) {
consensus_cflag_groups.push(group);
}
}
}
}
} else {
consensus_cflag_groups = Some(cflag_groups);
}
}
cc = consensus_cc
.expect("Entries are present in compile_commands.json")
.to_string();
cflags = shlex::try_join(consensus_cflag_groups.unwrap().iter().flatten().map(|s| *s))
.expect("Input is not expected to contain NUL characters");
let usemodule = {
#[cfg(not(feature = "riot-rs"))]
{
println!("cargo:rerun-if-env-changed=RIOT_USEMODULE");
// We tolerate the absence. Older versions of riot-wrappers would then fail to
// enable modules, but newer versions just work without it (and would need a dummy
// variable passed in otherwise). On the long run, this is going away anyway.
env::var("RIOT_USEMODULE").unwrap_or_default()
}
#[cfg(feature = "riot-rs")]
{
println!("cargo:rerun-if-env-changed=DEP_RIOT_BUILD_DIR");
let riot_builddir =
env::var("DEP_RIOT_BUILD_DIR").expect("DEP_RIOT_BUILD_DIR unset?");
get_riot_var(&riot_builddir, "USEMODULE")
}
};
for m in usemodule.split(" ") {
// Hack around https://github.com/RIOT-OS/RIOT/pull/16129#issuecomment-805810090
write!(
cflags,
" -DMODULE_{}",
m.to_uppercase()
// avoid producing MODULE_BOARDS_COMMON_SAMDX1-ARDUINO-BOOTLOADER
.replace('-', "_")
)
.unwrap();
}
} else {
cc = env::var("RIOT_CC")
.expect("Please pass in RIOT_CC; see README.md for details.")
.clone();
cflags = env::var("RIOT_CFLAGS")
.expect("Please pass in RIOT_CFLAGS; see README.md for details.");
}
// pass CC and CFLAGS to dependees
// this requires a `links = "riot-sys"` directive in Cargo.toml.
// Dependees can then access these as DEP_RIOT_SYS_CC and DEP_RIOT_SYS_CFLAGS.
println!("cargo:CC={}", &cc);
println!("cargo:CFLAGS={}", &cflags);
println!("cargo:rerun-if-changed=riot-bindgen.h");
let cflags = shlex::split(&cflags).expect("Odd shell escaping in RIOT_CFLAGS");
let cflags: Vec<String> = cflags
.into_iter()
.filter(|x| {
match x.as_ref() {
// These will be in riotbuild.h as well, and better there because bindgen emits
// consts for data from files but not from defines (?)
x if x.starts_with("-D") => false,
// Don't pollute the riot-sys source directory -- cargo is run unconditionally
// in the Makefiles, and this script tracks on its own which files to depend on
// for rebuilding.
"-MD" => false,
// accept all others
_ => true,
}
})
.collect();
let bindings = builder()
.header("riot-bindgen.h")
.clang_args(&cflags)
.use_core()
.ctypes_prefix("libc")
// We've traditionally used size_t explicitly and cast it in riot-wrappers; changing this
// now (going from bindgen 0.60 to 0.64) would break where it's used (although we still
// might instate a type alias for size_t later instead).
.size_t_is_usize(false)
.impl_debug(true)
// Structs listed here are Packed and thus need impl_debug, but also contain non-Copy
// members.
//
// This is a workaround for <https://github.com/rust-lang/rust-bindgen/issues/2221>; once
// that is fixed and our bindgen is updated, these can just go away again.
//
// If you see any errors like
//
// ```
// error: reference to packed field is unaligned
// --> .../out/bindings.rs:79797:13
// |
// 79797 | self.opcode, self.length, self.data
// | ^^^^^^^^^^^
// |
// = note: `#[deny(unaligned_references)]` on by default
// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
// = note: for more information, see issue #82523 <https://github.com/rust-lang/rust/issues/82523>
// = note: fields of packed structs are not properly aligned, and creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
// = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
// = note: this error originates in the macro `$crate::format_args` (in Nightly builds, run with -Z macro-backtrace for more info)
// ```
//
// please add the offending struct in here; if existing code depends on the Debug
// implementation, you may add a Debug implementation (that possibly is just a dummy, for
// in these cases it *is* hard to implement showing all details) to this crate for the
// duration of these workarounds.
.no_debug("ble_hci_cmd")
.no_debug("ble_hci_ev_command_complete")
.no_debug("ble_hci_ev_le_subev_big_complete")
.no_debug("ble_hci_ev_le_subev_big_sync_established")
.no_debug("ble_hci_ev_le_subev_periodic_adv_rpt")
.no_debug("ext_adv_report")
.derive_default(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let bindgen_outfilename = out_path.join("bindings.rs");
bindings
.write_to_file(&bindgen_outfilename)
.expect("Couldn't write bindings!");
// Store for inspection for markers; see there
let mut bindgen_output = Vec::<u8>::new();
bindings
.write(Box::new(&mut bindgen_output))
.expect("String writing never fails");
let bindgen_output = std::str::from_utf8(&bindgen_output).expect("Rust source code is UTF-8");
// Build a compile_commands.json, and run C2Rust
//
// The output is cleared beforehand (for c2rust no-ops when an output file is present), and the
// input is copied to OUT_DIR as that's the easiest way to get c2rust to put the output file in
// a different place -- and because some additions are generated anyway.
let c2rust_infile = "riot-c2rust.h";
// Follows from c2rust_infile and C2Rust's file name translation scheme
let c2rust_output = out_path.join("riot_c2rust.rs");
let headercopy = out_path.join(c2rust_infile);
println!("cargo:rerun-if-changed=riot-c2rust.h");
std::fs::copy("riot-headers.h", out_path.join("riot-headers.h"))
.expect("Failed to copy over header file");
// These constant initializers are unusable without knowledge of which type they're for; adding
// the information here to build explicit consts
let macro_functions = [
("SOCK_IPV4_EP_ANY", "sock_udp_ep_t", None, true, None),
("SOCK_IPV6_EP_ANY", "sock_udp_ep_t", None, true, None),
("MUTEX_INIT", "mutex_t", None, true, None),
// neither C2Rust nor bindgen understand the cast without help
("STATUS_NOT_FOUND", "thread_status_t", None, true, None),
// If any board is ever added that works completely differently, this'll have to go behind
// a feature-gate
(
"GPIO_PIN",
"gpio_t",
Some("unsigned port, unsigned pin"),
// would be nice to have them const, but on boards like samd21-xpro that'd require
// several nightly features (const_ptr_offset, const_mut_refs).
false,
None,
),
// These are bound to the signature already in periph_init.
("I2C_DEV", "i2c_t", Some("unsigned num"), false, None),
("SPI_DEV", "spi_t", Some("unsigned num"), false, None),
// No good source on why this sould have a fixed signature, but at this point it's a
// pattern.
("UART_DEV", "uart_t", Some("unsigned num"), false, None),
("PWM_DEV", "pwm_t", Some("unsigned num"), false, None),
("ADC_LINE", "adc_t", Some("unsigned num"), false, None),
("TIMER_DEV", "timer_t", Some("unsigned num"), false, None),
("QDEC_DEV", "qdec_t", Some("unsigned num"), false, None),
("DAC_LINE", "dac_t", Some("unsigned num"), false, None),
];
let mut macro_functions: Vec<_> = macro_functions
.iter()
.map(
|(macro_name, return_type, args, is_const, fallback_value)| {
(
macro_name.to_string(),
*return_type,
*args,
*is_const,
*fallback_value,
)
},
)
.collect();
for i in 0..8 {
macro_functions.push((format!("LED{}_ON", i), "void", None, false, None));
macro_functions.push((format!("LED{}_OFF", i), "void", None, false, None));
macro_functions.push((format!("LED{}_TOGGLE", i), "void", None, false, None));
macro_functions.push((
format!("LED{}_IS_PRESENT", i),
"int",
None,
true,
Some("-1"),
));
}
let mut c_code = String::new();
std::fs::File::open("riot-c2rust.h")
.expect("Failed to open riot-c2rust.h")
.read_to_string(&mut c_code)
.expect("Failed to read riot-c2rust.h");
for (macro_name, return_type, args, _is_const, fallback_value) in macro_functions.iter() {
let argnames = match args {
None => "".to_string(),
Some("void") => "()".to_string(),
Some(args) => format!(
"({})",
args.split(", ")
.map(|s| &s[s.find(" ").expect("Non-void args need to have names")..])
// Not really essential concepturally, but .join is only available on
// slices, not on Iterator
.collect::<Vec<&str>>()
.join(", ")
),
};
// The ifdef guards make errors easier to spot: A "cannot find function
// `macro_SOCK_IPV6_EP_ANY` in crate `riot_sys`" can lead one to check whether
// SOCK_IPV6_EP_ANY is really defined, whereas if the macro is missing, C2Rust would
// produce a run-time panic, and the compiler would reject that in a const function.
//
// This is more reliable than the previous approach of trying to defined a `-DSOME_MODULE`
// condition, also because there may not even be a module that gives a precise condition.
if *return_type == "void" {
// in C, assigning and returning void is special
write!(
c_code,
r"
#ifdef {macro_name}
{return_type} macro_{macro_name}({args}) {{
{macro_name}{argnames};
}}
",
return_type = return_type,
macro_name = macro_name,
args = args.unwrap_or("void"),
argnames = argnames,
)
} else {
write!(
c_code,
r"
#ifdef {macro_name}
{return_type} macro_{macro_name}({args}) {{
{return_type} result = {macro_name}{argnames};
return result;
}}
",
return_type = return_type,
macro_name = macro_name,
args = args.unwrap_or("void"),
argnames = argnames,
)
}
.unwrap();
if let Some(fallback_value) = fallback_value {
writeln!(
c_code,
r"
#else
{return_type} macro_{macro_name}({args}) {{
return {fallback_value};
}}
",
args = args.unwrap_or("void"),
)
.unwrap();
}
writeln!(c_code, r" #endif").unwrap();
}
let mut outfile =
std::fs::File::create(&headercopy).expect("Failed to open temporary riot-c2rust.h");
outfile
.write_all(c_code.as_bytes())
.expect("Failed to write to riot-c2rust.h");
outfile
.sync_all()
.expect("failed to write to riot-c2rust.h");
if cc.find("clang") == None {
panic!("riot-sys only accepts clang style CFLAGS. RIOT can produce them using the compile_commands tool even when using a non-clang compiler, such as GCC.");
};
let arguments: Vec<_> = core::iter::once("any-cc".to_string())
.chain(cflags.into_iter())
.chain(core::iter::once(c2rust_infile.to_string()))
.collect();
let compile_commands = json!([{
"arguments": arguments,
"directory": out_path,
"file": c2rust_infile,
}]);
let compile_commands_name = out_path.join("compile_commands.json");
let mut compile_commands_file = std::fs::File::create(compile_commands_name.clone())
.expect("Failed to create compile_commands.json");
serde_json::to_writer_pretty(&mut compile_commands_file, &compile_commands)
.expect("Failed to write to compile_commands.json");
compile_commands_file
.sync_all()
.expect("Failed to write to compile_commands.json");
let compile_commands_name = compile_commands_name
.to_str()
.expect("Inexpressible path name");
println!("cargo:rerun-if-env-changed=C2RUST");
println!("cargo:rerun-if-env-changed=PATH");
let c2rust = std::env::var("C2RUST").unwrap_or_else(|_| "c2rust".to_string());
let c2rust_version = std::process::Command::new(&c2rust)
.args(&["--version"])
.output()
.expect("C2Rust version check did not complete")
.stdout;
let c2rust_version = String::from_utf8_lossy(&c2rust_version);
print!("C2Rust binary {}, version: {}", c2rust, c2rust_version);
// FIXME: This does not rat on the used files. Most are probably included from riot-bindgen.h
// anyway, tough.
println!("Running C2Rust on {}", compile_commands_name);
let status = std::process::Command::new(&c2rust)
.args(&[
"transpile",
compile_commands_name,
"--preserve-unused-functions",
"--emit-modules",
"--emit-no-std",
"--translate-const-macros",
"--overwrite-existing",
"--fail-on-error",
])
.status()
.expect("C2Rust failed");
if !status.success() {
println!(
"cargo:warning=C2Rust failed with error code {}, exiting",
status
);
std::process::exit(status.code().unwrap_or(1));
}
// Some fix-ups to the C2Rust output
// (could just as well call sed...)
use std::io::{Read, Write};
let mut rustcode = String::new();
std::fs::File::open(c2rust_output)
.expect("Failed to open riot_c2rust.rs")
.read_to_string(&mut rustcode)
.expect("Failed to read from riot_c2rust.rs");
rustcode = rustcode.replace("use ::libc;\n", "");
if !c2rust_version.contains("+git-for-riot") && c2rust_version.contains("C2Rust 0.15") {
// Old C2Rust still generate old-style ASM -- workaround for https://github.com/immunant/c2rust/issues/306
rustcode = rustcode.replace(" asm!(", " llvm_asm!(");
}
// Workaround for https://github.com/immunant/c2rust/issues/372
rustcode = rustcode.replace("::core::intrinsics::", "crate::intrinsics_replacements::");
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_KEEP_EXTERN_TYPES");
if env::var("CARGO_FEATURE_KEEP_EXTERN_TYPES").is_err() {
// For documentation on why we do this, see include in src/inline.rs.
let pubtypepattern = regex::Regex::new("pub type (?P<type>[a-zA-Z0-9_]+);").unwrap();
let pubtypes = pubtypepattern
.captures_iter(&rustcode)
.map(|m| m.name("type").unwrap().as_str());
let pubtype_replacements = out_path.join("pubtype_replacements.rs");
let mut pubtype_replacements_file = std::fs::File::create(pubtype_replacements)
.expect("Failed to create pubtype_replacements.rs");
for pt in pubtypes {
writeln!(
pubtype_replacements_file,
"pub type {} = [u8; isize::MAX as _];",
pt
)
.expect("Failed to write to pubtype_replacements.rs");
}
rustcode = pubtypepattern
.replace_all(&rustcode, "/* $0 */")
.to_string();
}
// On 64-bit native, what gets emitted as vprintf(_, _, _: __builtin_va_list) gets emitted as
// vprintf(_, _, _: core::ffi::VaList), which is unsupported in stable -- but we don't use that
// function, it's just an unfortunate side effect of --preserve-unused-functions. This quick
// workaround enables building and ensures that the function is never called.
rustcode = rustcode.replace("::core::ffi::VaList", "::core::convert::Infallible");
rustcode = rustcode.replace("__arg.as_va_list()", "__arg");
// Replace the function declarations with ... usually something pub, but special considerations
// may apply
let mut rustcode_functionsreplaced = String::new();
let function_original_prefix = r#"unsafe extern "C" fn "#;
let mut functionchunks = rustcode.split(function_original_prefix);
rustcode_functionsreplaced.push_str(
functionchunks
.next()
.expect("Split produces at least a hit"),
);
for chunk in functionchunks {
let funcname = &chunk[..chunk.find('(').expect("Function has parentheses somewhere")];
let macro_details = if funcname.len() > 5 && &funcname[..6] == "macro_" {
macro_functions
.iter()
.filter(|(macro_name, _, _, _, _)| &funcname[6..] == *macro_name)
.next()
} else {
None
};
let new_prefix = match (funcname, macro_details) {
// used as a callback, therefore does need the extern "C" -- FIXME probably worth a RIOT issue
("_evtimer_msg_handler" | "_evtimer_mbox_handler", _) => function_original_prefix,
// Assigned by CMSIS to the const that is being overridden and thus needs its original
// "C" type; see also riot-c2rust.h. (Actually using it would cause a linker error
// anyway).
("__masked_builtin_arm_get_fpscr" | "__masked_builtin_arm_set_fpscr", _) => {
function_original_prefix
}
// same problem but from C2Rust's --translate-const-macros
("__NVIC_SetPriority", _) => function_original_prefix,
// As below (no need for extern), and they are const as declared ni the macro_functions
// list.
(_, Some((_, _, _, is_const, _))) => {
// No "pub" because that's already a "pub" in front of it, they were never static
match is_const {
// FIXME: These should be unsafe -- just because most of them are const doesn't
// necessrily mean they're safe (just the first few happened to be, but that's
// not this crate's place to assert)
true => "const unsafe fn ",
false => "unsafe fn ",
}
}
// C2Rust transpiles these into Rust with conflicting lifetimes, see
// https://github.com/immunant/c2rust/issues/309
//
// Simply disabling them here because they aren't used by any other inline code (and
// will, when the manual llvm_asm to asm changes are added to riot-sys, not have manual
// asm conversions on top of that).
("__SMLALD" | "__SMLALDX" | "__SMLSLD" | "__SMLSLDX", _) => {
"#[cfg(c2rust_fixed_309)]\npub unsafe fn "
}
// The rest we don't need to call through the extern convention, but let's please make
// them pub to be usable
_ => "pub unsafe fn ",
};
rustcode_functionsreplaced.push_str(new_prefix);
rustcode_functionsreplaced.push_str(chunk);
}
rustcode = rustcode_functionsreplaced;
let output_replaced = out_path.join("riot_c2rust_replaced.rs");
std::fs::File::create(output_replaced)
.expect("Failed to create riot_c2rust_replaced.rs")
.write(rustcode.as_bytes())
.expect("Failed to write to riot_c2rust_replaced.rs");
// Pub uses of inline right into the main lib.rs
//
// This is primarily for things that can really come from either backend (eg. irq functions
// that are regular on native but static inline on others), and for convenience stuff like
// macro_.
//
// Some functions are also in because they're innocuous enough.
//
// If (eg. on some platform but not on others) any function here is not an inline function,
// that does not hurt; the entry doesn't do anything on these then. (But it is especially
// valuable, as it ensures that on the *other* platforms it's still available with the same
// Rust name).
let mut toplevel_from_inline: Vec<String> = [
"bluetil_ad_add_flags",
"coap_get_code_raw",
"coap_get_total_hdr_len",
"gnrc_netapi_dispatch_send",
"gnrc_netif_ipv6_addrs_get",
"gnrc_netreg_entry_init_pid",
"gpio_is_valid",
"irq_disable",
"irq_is_enabled",
"irq_is_in",
"irq_restore",
"mutex_trylock",
"mutex_lock",
"pid_is_valid",
"shell_run_forever",
"sock_udp_recv",
"sock_udp_send",
"thread_get",
"thread_getpid",
"thread_get_unchecked",
"ztimer_spin",
// because when defined through RIOT's af.h these are enums and thus unix_af_t prefixed.
"AF_UNSPEC",
"AF_UNIX",
"AF_PACKET",
"AF_INET",
"AF_INET6",
]
.iter()
.map(|name| name.to_string())
.collect();
for (macro_name, _, _, _, _) in macro_functions.iter() {
toplevel_from_inline.push(format!("macro_{}", macro_name));
}
let toplevel_from_inline: Vec<String> = toplevel_from_inline
.drain(..)
.filter(|s: &String| {
// Not just matching on `pub fn`: `irq_disable` on native is visible as `extern "C" {
// fn irq_disable(); }`, and that should not trigger going through C2Rust.
rustcode.contains(&format!("pub fn {}(", s))
|| rustcode.contains(&format!("unsafe fn {}(", s))
|| rustcode.contains(&format!(" {}: ", s))
})
.collect();
let toplevel_from_inline_filename = out_path.join("toplevel_from_inline.rs");
std::fs::File::create(toplevel_from_inline_filename)
.expect("Failed to create toplevel_from_inline.rs")
.write(
format!(
"
pub use inline::{{ {} }};
",
toplevel_from_inline.join(",\n")
)
.as_bytes(),
)
.expect("Failed to write to toplevel_from_inline.rs");
enum MarkerCondition {
/// This has been around for long enough that no actual check is performed any more, the
/// marker is just always set. Markers are set to that when the oldest supported RIOT
/// version has the new behavior; users of riot-sys may stop checking for the marker when
/// they depend on a riot-sys version that has it on Always.
Always,
/// A marker that has been around for some time during while preparing some PRs, but never
/// was merged, and the PR was abandoned.
///
/// This is equivalent to not having the marker in the first place, except that their
/// presence serves as a reminder to not reuse that marker name.
Never,
/// A marker that is set if its name is found in the bindgen output. Shorthand for
/// Text(name).
NameInCode,
}
use MarkerCondition::*;
let markers = [
// See https://github.com/RIOT-OS/RIOT/pull/17569, available after 2022.01
(Always, "phydat_unit_t"),
// See https://github.com/RIOT-OS/RIOT/pull/17660, available after 2022.01
(Always, "vfs_iterate_mount_dirs"),
// See https://github.com/RIOT-OS/RIOT/pull/17758 retrofitting it for the change in
// https://github.com/RIOT-OS/RIOT/pull/17351, available in 2022.04
(Always, "ztimer_periodic_callback_t"),
// Experimental markers
//
// These are not merged in RIOT yet, but promising candidates; if there are any substantial
// changes to them, their marker name will be bumped, but it is expected that they will be
// moved up and get an "available after" release once merged.
// See https://github.com/RIOT-OS/RIOT/pull/17544
(Never, "coap_build_pkt_t"),
(Never, "gcoap_resource_t"),
// See https://github.com/RIOT-OS/RIOT/pull/17957, available TBD
(NameInCode, "coap_request_ctx_t"),
];
for (needle, name) in markers {
let found = match needle {
NameInCode => bindgen_output.contains(name),
Always => true,
Never => false,
};
if found {
println!("cargo:MARKER_{}=1", name);
}
}
// let downstream crates know we're building for riot-rs
#[cfg(feature = "riot-rs")]
println!("cargo:MARKER_riot_rs=1");
println!(
"cargo:BINDGEN_OUTPUT_FILE={}",
bindgen_outfilename.display()
);
}
#[cfg(feature = "riot-rs")]
fn get_riot_var(riot_builddir: &str, var: &str) -> String {
let output = std::process::Command::new("sh")
.arg("-c")
.arg(format!(
"{} make --no-print-directory -C {} TOOLCHAIN=llvm info-debug-variable-{}",
"WARNING_EXTERNAL_MODULE_DIRS=0", riot_builddir, var
))
.output()
.unwrap()
.stdout;
String::from_utf8_lossy(output.as_slice()).trim_end().into()
}