1//! This crate is the `cpp` cargo build script implementation. It is useless
2//! without the companion crates `cpp`, and `cpp_macro`.
3//!
4//! For more information, see the
5//! [`cpp` crate module level documentation](https://docs.rs/cpp).
6
7#![allow(clippy::write_with_newline)]
8
9mod strnom;
10
11use cpp_common::*;
12use lazy_static::lazy_static;
13use std::collections::hash_map::{Entry, HashMap};
14use std::env;
15use std::fs::{create_dir, remove_dir_all, File};
16use std::io::prelude::*;
17use std::path::{Path, PathBuf};
18
19mod parser;
20
21fn warnln_impl(a: &str) {
22 for s: &str in a.lines() {
23 println!("cargo:warning={}", s);
24 }
25}
26
27macro_rules! warnln {
28 ($($all:tt)*) => {
29 $crate::warnln_impl(&format!($($all)*));
30 }
31}
32
33// Like the write! macro, but add the #line directive (pointing to this file).
34// Note: the string literal must be on on the same line of the macro
35macro_rules! write_add_line {
36 ($o:expr, $($e:tt)*) => {
37 (|| {
38 writeln!($o, "#line {} \"{}\"", line!(), file!().replace('\\', "\\\\"))?;
39 write!($o, $($e)*)
40 })()
41 };
42}
43
44const INTERNAL_CPP_STRUCTS: &str = r#"
45/* THIS FILE IS GENERATED BY rust-cpp. DO NOT EDIT */
46
47#include "stdint.h" // For {u}intN_t
48#include <new> // For placement new
49#include <cstdlib> // For abort
50#include <type_traits>
51#include <utility>
52
53namespace rustcpp {
54
55// We can't just pass or return any type from extern "C" rust functions (because the call
56// convention may differ between the C++ type, and the Rust type).
57// So we make sure to pass trivial structure that only contains a pointer to the object we want to
58// pass. The constructor of these helper class contains a 'container' of the right size which will
59// be allocated on the stack.
60template<typename T> struct return_helper {
61 struct container {
62#if defined (_MSC_VER) && (_MSC_VER + 0 < 1900)
63 char memory[sizeof(T)];
64 ~container() { reinterpret_cast<T*>(this)->~T(); }
65#else
66 // The fact that it is in an union means it is properly sized and aligned, but we have
67 // to call the destructor and constructor manually
68 union { T memory; };
69 ~container() { memory.~T(); }
70#endif
71 container() {}
72 };
73 const container* data;
74 return_helper(int, const container &c = container()) : data(&c) { }
75};
76
77template<typename T> struct argument_helper {
78 using type = const T&;
79};
80template<typename T> struct argument_helper<T&> {
81 T &ref;
82 argument_helper(T &x) : ref(x) {}
83 using type = argument_helper<T&> const&;
84};
85
86template<typename T>
87typename std::enable_if<std::is_copy_constructible<T>::value>::type copy_helper(const void *src, void *dest)
88{ new (dest) T (*static_cast<T const*>(src)); }
89template<typename T>
90typename std::enable_if<!std::is_copy_constructible<T>::value>::type copy_helper(const void *, void *)
91{ std::abort(); }
92template<typename T>
93typename std::enable_if<std::is_default_constructible<T>::value>::type default_helper(void *dest)
94{ new (dest) T(); }
95template<typename T>
96typename std::enable_if<!std::is_default_constructible<T>::value>::type default_helper(void *)
97{ std::abort(); }
98
99template<typename T> int compare_helper(const T &a, const T&b, int cmp) {
100 switch (cmp) {
101 using namespace std::rel_ops;
102 case 0:
103 if (a < b)
104 return -1;
105 if (b < a)
106 return 1;
107 return 0;
108 case -2: return a < b;
109 case 2: return a > b;
110 case -1: return a <= b;
111 case 1: return a >= b;
112 }
113 std::abort();
114}
115}
116
117#define RUST_CPP_CLASS_HELPER(HASH, ...) \
118 extern "C" { \
119 void __cpp_destructor_##HASH(void *ptr) { typedef __VA_ARGS__ T; static_cast<T*>(ptr)->~T(); } \
120 void __cpp_copy_##HASH(const void *src, void *dest) { rustcpp::copy_helper<__VA_ARGS__>(src, dest); } \
121 void __cpp_default_##HASH(void *dest) { rustcpp::default_helper<__VA_ARGS__>(dest); } \
122 }
123"#;
124
125lazy_static! {
126 static ref CPP_DIR: PathBuf = OUT_DIR.join("rust_cpp");
127 static ref CARGO_MANIFEST_DIR: PathBuf = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect(
128 r#"
129-- rust-cpp fatal error --
130
131The CARGO_MANIFEST_DIR environment variable was not set.
132NOTE: rust-cpp's build function must be run in a build script."#
133 ));
134}
135
136fn gen_cpp_lib(visitor: &parser::Parser) -> PathBuf {
137 let result_path = CPP_DIR.join("cpp_closures.cpp");
138 let mut output = File::create(&result_path).expect("Unable to generate temporary C++ file");
139
140 write!(output, "{}", INTERNAL_CPP_STRUCTS).unwrap();
141
142 if visitor.callbacks_count > 0 {
143 #[rustfmt::skip]
144 write_add_line!(output, r#"
145extern "C" {{
146 void (*rust_cpp_callbacks{file_hash}[{callbacks_count}])() = {{}};
147}}
148 "#,
149 file_hash = *FILE_HASH,
150 callbacks_count = visitor.callbacks_count
151 ).unwrap();
152 }
153
154 write!(output, "{}\n\n", &visitor.snippets).unwrap();
155
156 let mut hashmap = HashMap::new();
157
158 let mut sizealign = vec![];
159 for Closure { body_str, sig, callback_offset, .. } in &visitor.closures {
160 let ClosureSig { captures, cpp, .. } = sig;
161
162 let hash = sig.name_hash();
163 let name = sig.extern_name();
164
165 match hashmap.entry(hash) {
166 Entry::Occupied(e) => {
167 if *e.get() != sig {
168 // Let the compiler do a compilation error. FIXME: report a better error
169 warnln!("Hash collision detected.");
170 } else {
171 continue;
172 }
173 }
174 Entry::Vacant(e) => {
175 e.insert(sig);
176 }
177 }
178
179 let is_void = cpp == "void";
180
181 // Generate the sizes array with the sizes of each of the argument types
182 if is_void {
183 sizealign.push(format!(
184 "{{{hash}ull, 0, 1, {callback_offset}ull << 32}}",
185 hash = hash,
186 callback_offset = callback_offset
187 ));
188 } else {
189 sizealign.push(format!("{{
190 {hash}ull,
191 sizeof({type}),
192 rustcpp::AlignOf<{type}>::value,
193 rustcpp::Flags<{type}>::value | {callback_offset}ull << 32
194 }}", hash=hash, type=cpp, callback_offset = callback_offset));
195 }
196 for Capture { cpp, .. } in captures {
197 sizealign.push(format!("{{
198 {hash}ull,
199 sizeof({type}),
200 rustcpp::AlignOf<{type}>::value,
201 rustcpp::Flags<{type}>::value
202 }}", hash=hash, type=cpp));
203 }
204
205 // Generate the parameters and function declaration
206 let params = captures
207 .iter()
208 .map(|&Capture { mutable, ref name, ref cpp }| {
209 if mutable {
210 format!("{} & {}", cpp, name)
211 } else {
212 format!("{} const& {}", cpp, name)
213 }
214 })
215 .collect::<Vec<_>>()
216 .join(", ");
217
218 if is_void {
219 #[rustfmt::skip]
220 write_add_line!(output, r#"
221extern "C" {{
222void {name}({params}) {{
223{body}
224}}
225}}
226"#,
227 name = &name,
228 params = params,
229 body = body_str
230 ).unwrap();
231 } else {
232 let comma = if params.is_empty() { "" } else { "," };
233 let args = captures
234 .iter()
235 .map(|Capture { name, .. }| name.to_string())
236 .collect::<Vec<_>>()
237 .join(", ");
238 #[rustfmt::skip]
239 write_add_line!(output, r#"
240static inline {ty} {name}_impl({params}) {{
241{body}
242}}
243extern "C" {{
244void {name}({params}{comma} void* __result) {{
245 ::new(__result) ({ty})({name}_impl({args}));
246}}
247}}
248"#,
249 name = &name,
250 params = params,
251 comma = comma,
252 ty = cpp,
253 args = args,
254 body = body_str
255 ).unwrap();
256 }
257 }
258
259 for class in &visitor.classes {
260 let hash = class.name_hash();
261
262 // Generate the sizes array
263 sizealign.push(format!("{{
264 {hash}ull,
265 sizeof({type}),
266 rustcpp::AlignOf<{type}>::value,
267 rustcpp::Flags<{type}>::value
268 }}", hash=hash, type=class.cpp));
269
270 // Generate helper function.
271 // (this is done in a macro, which right after a #line directing pointing to the location of
272 // the cpp_class! macro in order to give right line information in the possible errors)
273 write!(
274 output,
275 "{line}RUST_CPP_CLASS_HELPER({hash}, {cpp_name})\n",
276 line = class.line,
277 hash = hash,
278 cpp_name = class.cpp
279 )
280 .unwrap();
281
282 if class.derives("PartialEq") {
283 write!(output,
284 "{line}extern \"C\" bool __cpp_equal_{hash}(const {name} *a, const {name} *b) {{ return *a == *b; }}\n",
285 line = class.line, hash = hash, name = class.cpp).unwrap();
286 }
287 if class.derives("PartialOrd") {
288 write!(output,
289 "{line}extern \"C\" bool __cpp_compare_{hash}(const {name} *a, const {name} *b, int cmp) {{ return rustcpp::compare_helper(*a, *b, cmp); }}\n",
290 line = class.line, hash = hash, name = class.cpp).unwrap();
291 }
292 }
293
294 let mut magic = vec![];
295 for mag in STRUCT_METADATA_MAGIC.iter() {
296 magic.push(format!("{}", mag));
297 }
298
299 #[rustfmt::skip]
300 write_add_line!(output, r#"
301
302namespace rustcpp {{
303
304template<typename T>
305struct AlignOf {{
306 struct Inner {{
307 char a;
308 T b;
309 }};
310 static const uintptr_t value = sizeof(Inner) - sizeof(T);
311}};
312
313template<typename T>
314struct Flags {{
315 static const uintptr_t value =
316 (std::is_copy_constructible<T>::value << {flag_is_copy_constructible}) |
317 (std::is_default_constructible<T>::value << {flag_is_default_constructible}) |
318#if !defined(__GNUC__) || (__GNUC__ + 0 >= 5) || defined(__clang__)
319 (std::is_trivially_destructible<T>::value << {flag_is_trivially_destructible}) |
320 (std::is_trivially_copyable<T>::value << {flag_is_trivially_copyable}) |
321 (std::is_trivially_default_constructible<T>::value << {flag_is_trivially_default_constructible}) |
322#endif
323 0;
324}};
325
326struct SizeAlign {{
327 uint64_t hash;
328 uint64_t size;
329 uint64_t align;
330 uint64_t flags;
331}};
332
333struct MetaData {{
334 uint8_t magic[128];
335 uint8_t version[16];
336 uint64_t endianness_check;
337 uint64_t length;
338 SizeAlign data[{length}];
339}};
340
341MetaData metadata_{hash} = {{
342 {{ {magic} }},
343 "{version}",
344 0xffef,
345 {length},
346 {{ {data} }}
347}};
348
349}} // namespace rustcpp
350"#,
351 hash = *FILE_HASH,
352 data = sizealign.join(", "),
353 length = sizealign.len(),
354 magic = magic.join(", "),
355 version = VERSION,
356 flag_is_copy_constructible = flags::IS_COPY_CONSTRUCTIBLE,
357 flag_is_default_constructible = flags::IS_DEFAULT_CONSTRUCTIBLE,
358 flag_is_trivially_destructible = flags::IS_TRIVIALLY_DESTRUCTIBLE,
359 flag_is_trivially_copyable = flags::IS_TRIVIALLY_COPYABLE,
360 flag_is_trivially_default_constructible = flags::IS_TRIVIALLY_DEFAULT_CONSTRUCTIBLE,
361 ).unwrap();
362
363 result_path
364}
365
366fn clean_artifacts() {
367 if CPP_DIR.is_dir() {
368 remove_dir_all(&*CPP_DIR).expect(
369 msg:r#"
370msg:-- rust-cpp fatal error --
371msg:
372msg:Failed to remove existing build artifacts from output directory."#,
373 );
374 }
375
376 create_dir(&*CPP_DIR).expect(
377 msg:r#"
378msg:-- rust-cpp fatal error --
379msg:
380msg:Failed to create output object directory."#,
381 );
382}
383
384/// This struct is for advanced users of the build script. It allows providing
385/// configuration options to `cpp` and the compiler when it is used to build.
386///
387/// ## API Note
388///
389/// Internally, `cpp` uses the `cc` crate to build the compilation artifact,
390/// and many of the methods defined on this type directly proxy to an internal
391/// `cc::Build` object.
392pub struct Config {
393 cc: cc::Build,
394 std_flag_set: bool, // true if the -std flag was specified
395}
396
397impl Default for Config {
398 fn default() -> Self {
399 Config::new()
400 }
401}
402
403impl Config {
404 /// Create a new `Config` object. This object will hold the configuration
405 /// options which control the build. If you don't need to make any changes,
406 /// `cpp_build::build` is a wrapper function around this interface.
407 pub fn new() -> Config {
408 let mut cc = cc::Build::new();
409 cc.cpp(true).include(&*CARGO_MANIFEST_DIR);
410 Config { cc, std_flag_set: false }
411 }
412
413 /// Add a directory to the `-I` or include path for headers
414 pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
415 self.cc.include(dir);
416 self
417 }
418
419 /// Specify a `-D` variable with an optional value
420 pub fn define(&mut self, var: &str, val: Option<&str>) -> &mut Self {
421 self.cc.define(var, val);
422 self
423 }
424
425 // XXX: Make sure that this works with sizes logic
426 /// Add an arbitrary object file to link in
427 pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Self {
428 self.cc.object(obj);
429 self
430 }
431
432 /// Add an arbitrary flag to the invocation of the compiler
433 pub fn flag(&mut self, flag: &str) -> &mut Self {
434 if flag.starts_with("-std=") {
435 self.std_flag_set = true;
436 }
437 self.cc.flag(flag);
438 self
439 }
440
441 /// Add an arbitrary flag to the invocation of the compiler if it supports it
442 pub fn flag_if_supported(&mut self, flag: &str) -> &mut Self {
443 if flag.starts_with("-std=") {
444 self.std_flag_set = true;
445 }
446 self.cc.flag_if_supported(flag);
447 self
448 }
449
450 // XXX: Make sure this works with sizes logic
451 /// Add a file which will be compiled
452 pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Self {
453 self.cc.file(p);
454 self
455 }
456
457 /// Set the standard library to link against when compiling with C++
458 /// support.
459 ///
460 /// The default value of this property depends on the current target: On
461 /// OS X `Some("c++")` is used, when compiling for a Visual Studio based
462 /// target `None` is used and for other targets `Some("stdc++")` is used.
463 ///
464 /// A value of `None` indicates that no automatic linking should happen,
465 /// otherwise cargo will link against the specified library.
466 ///
467 /// The given library name must not contain the `lib` prefix.
468 pub fn cpp_link_stdlib(&mut self, cpp_link_stdlib: Option<&str>) -> &mut Self {
469 self.cc.cpp_link_stdlib(cpp_link_stdlib);
470 self
471 }
472
473 /// Force the C++ compiler to use the specified standard library.
474 ///
475 /// Setting this option will automatically set `cpp_link_stdlib` to the same
476 /// value.
477 ///
478 /// The default value of this option is always `None`.
479 ///
480 /// This option has no effect when compiling for a Visual Studio based
481 /// target.
482 ///
483 /// This option sets the `-stdlib` flag, which is only supported by some
484 /// compilers (clang, icc) but not by others (gcc). The library will not
485 /// detect which compiler is used, as such it is the responsibility of the
486 /// caller to ensure that this option is only used in conjuction with a
487 /// compiler which supports the `-stdlib` flag.
488 ///
489 /// A value of `None` indicates that no specific C++ standard library should
490 /// be used, otherwise `-stdlib` is added to the compile invocation.
491 ///
492 /// The given library name must not contain the `lib` prefix.
493 pub fn cpp_set_stdlib(&mut self, cpp_set_stdlib: Option<&str>) -> &mut Self {
494 self.cc.cpp_set_stdlib(cpp_set_stdlib);
495 self
496 }
497
498 // XXX: Add support for custom targets
499 //
500 // /// Configures the target this configuration will be compiling for.
501 // ///
502 // /// This option is automatically scraped from the `TARGET` environment
503 // /// variable by build scripts, so it's not required to call this function.
504 // pub fn target(&mut self, target: &str) -> &mut Self {
505 // self.cc.target(target);
506 // self
507 // }
508
509 /// Configures the host assumed by this configuration.
510 ///
511 /// This option is automatically scraped from the `HOST` environment
512 /// variable by build scripts, so it's not required to call this function.
513 pub fn host(&mut self, host: &str) -> &mut Self {
514 self.cc.host(host);
515 self
516 }
517
518 /// Configures the optimization level of the generated object files.
519 ///
520 /// This option is automatically scraped from the `OPT_LEVEL` environment
521 /// variable by build scripts, so it's not required to call this function.
522 pub fn opt_level(&mut self, opt_level: u32) -> &mut Self {
523 self.cc.opt_level(opt_level);
524 self
525 }
526
527 /// Configures the optimization level of the generated object files.
528 ///
529 /// This option is automatically scraped from the `OPT_LEVEL` environment
530 /// variable by build scripts, so it's not required to call this function.
531 pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Self {
532 self.cc.opt_level_str(opt_level);
533 self
534 }
535
536 /// Configures whether the compiler will emit debug information when
537 /// generating object files.
538 ///
539 /// This option is automatically scraped from the `PROFILE` environment
540 /// variable by build scripts (only enabled when the profile is "debug"), so
541 /// it's not required to call this function.
542 pub fn debug(&mut self, debug: bool) -> &mut Self {
543 self.cc.debug(debug);
544 self
545 }
546
547 // XXX: Add support for custom out_dir
548 //
549 // /// Configures the output directory where all object files and static
550 // /// libraries will be located.
551 // ///
552 // /// This option is automatically scraped from the `OUT_DIR` environment
553 // /// variable by build scripts, so it's not required to call this function.
554 // pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Self {
555 // self.cc.out_dir(out_dir);
556 // self
557 // }
558
559 /// Configures the compiler to be used to produce output.
560 ///
561 /// This option is automatically determined from the target platform or a
562 /// number of environment variables, so it's not required to call this
563 /// function.
564 pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Self {
565 self.cc.compiler(compiler);
566 self
567 }
568
569 /// Configures the tool used to assemble archives.
570 ///
571 /// This option is automatically determined from the target platform or a
572 /// number of environment variables, so it's not required to call this
573 /// function.
574 pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Self {
575 self.cc.archiver(archiver);
576 self
577 }
578
579 /// Define whether metadata should be emitted for cargo allowing it to
580 /// automatically link the binary. Defaults to `true`.
581 pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Self {
582 // XXX: Use this to control the cargo metadata which rust-cpp produces
583 self.cc.cargo_metadata(cargo_metadata);
584 self
585 }
586
587 /// Configures whether the compiler will emit position independent code.
588 ///
589 /// This option defaults to `false` for `i686` and `windows-gnu` targets and
590 /// to `true` for all other targets.
591 pub fn pic(&mut self, pic: bool) -> &mut Self {
592 self.cc.pic(pic);
593 self
594 }
595
596 /// Extracts `cpp` declarations from the passed-in crate root, and builds
597 /// the associated static library to be linked in to the final binary.
598 ///
599 /// This method does not perform rust codegen - that is performed by `cpp`
600 /// and `cpp_macros`, which perform the actual procedural macro expansion.
601 ///
602 /// This method may technically be called more than once for ergonomic
603 /// reasons, but that usually won't do what you want. Use a different
604 /// `Config` object each time you want to build a crate.
605 pub fn build<P: AsRef<Path>>(&mut self, crate_root: P) {
606 assert_eq!(
607 env!("CARGO_PKG_VERSION"),
608 VERSION,
609 "Internal Error: mismatched cpp_common and cpp_build versions"
610 );
611
612 // Clean up any leftover artifacts
613 clean_artifacts();
614
615 // Parse the crate
616 let mut visitor = parser::Parser::default();
617 if let Err(err) = visitor.parse_crate(crate_root.as_ref().to_owned()) {
618 warnln!(
619 r#"-- rust-cpp parse error --
620There was an error parsing the crate for the rust-cpp build script:
621{}
622In order to provide a better error message, the build script will exit successfully, such that rustc can provide an error message."#,
623 err
624 );
625 return;
626 }
627
628 // Generate the C++ library code
629 let filename = gen_cpp_lib(&visitor);
630
631 // Ensure C++11 mode is enabled. We rely on some C++11 construct, so we
632 // must enable C++11 by default.
633 // MSVC, GCC >= 5, Clang >= 6 defaults to C++14, but since we want to
634 // supports older compiler which defaults to C++98, we need to
635 // explicitly set the "-std" flag.
636 // Ideally should be done by https://github.com/alexcrichton/cc-rs/issues/191
637 if !self.std_flag_set {
638 self.cc.flag_if_supported("-std=c++11");
639 }
640 // Build the C++ library
641 if let Err(e) = self.cc.file(filename).try_compile(LIB_NAME) {
642 let _ = writeln!(std::io::stderr(), "\n\nerror occurred: {}\n\n", e);
643 #[cfg(not(feature = "docs-only"))]
644 std::process::exit(1);
645 }
646 }
647}
648
649/// Run the `cpp` build process on the crate with a root at the given path.
650/// Intended to be used within `build.rs` files.
651pub fn build<P: AsRef<Path>>(path: P) {
652 Config::new().build(crate_root:path)
653}
654