1use std::{
2 env,
3 fs::{self, File},
4 io::Write,
5 path::{Path, PathBuf},
6};
7
8fn add_cpp_sources(
9 build: &mut cc::Build,
10 root: impl AsRef<Path>,
11 files: &[&str],
12) {
13 let root = root.as_ref();
14 build.files(files.iter().map(|src| {
15 let mut p = root.join(src);
16 p.set_extension("cpp");
17 p
18 }));
19
20 build.include(root);
21}
22
23fn add_c_sources(
24 build: &mut cc::Build,
25 root: impl AsRef<Path>,
26 files: &[&str],
27) {
28 let root = root.as_ref();
29 build.files(files.iter().map(|src| {
30 let mut p = root.join(src);
31 p.set_extension("c");
32 p
33 }));
34
35 build.include(root);
36}
37
38fn rename_libzmq_in_dir<D, N>(dir: D, new_name: N) -> Result<(), ()>
41where
42 D: AsRef<Path>,
43 N: AsRef<Path>,
44{
45 let dir = dir.as_ref();
46 let new_name = new_name.as_ref();
47
48 for entry in fs::read_dir(dir).unwrap() {
49 let file_name = entry.unwrap().file_name();
50 if file_name.to_string_lossy().starts_with("libzmq") {
51 fs::rename(dir.join(file_name), dir.join(new_name)).unwrap();
52 return Ok(());
53 }
54 }
55
56 Err(())
57}
58
59mod glibc {
60 use std::{
61 env,
62 path::{Path, PathBuf},
63 };
64
65 pub(crate) fn has_strlcpy() -> bool {
68 let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/strlcpy.c");
69 println!("cargo:rerun-if-changed={}", src.display());
70
71 let dest =
72 PathBuf::from(env::var("OUT_DIR").unwrap()).join("has_strlcpy");
73
74 cc::Build::new()
75 .warnings(false)
76 .get_compiler()
77 .to_command()
78 .arg(src)
79 .arg("-o")
80 .arg(dest)
81 .status()
82 .expect("failed to execute gcc")
83 .success()
84 }
85}
86
87mod cxx11 {
88 use std::{
89 env,
90 path::{Path, PathBuf},
91 };
92
93 pub(crate) fn has_cxx11() -> bool {
96 let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/trivial.c");
97 println!("cargo:rerun-if-changed={}", src.display());
98
99 let dest =
100 PathBuf::from(env::var("OUT_DIR").unwrap()).join("has_cxx11");
101
102 cc::Build::new()
103 .cpp(true)
104 .warnings(true)
105 .warnings_into_errors(true)
106 .std("c++11")
107 .get_compiler()
108 .to_command()
109 .arg(src)
110 .arg("-o")
111 .arg(dest)
112 .status()
113 .expect("failed to execute gcc")
114 .success()
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct LibLocation {
121 include_dir: PathBuf,
122 lib_dir: PathBuf,
123}
124
125impl LibLocation {
126 pub fn new<L, I>(lib_dir: L, include_dir: I) -> Self
128 where
129 L: Into<PathBuf>,
130 I: Into<PathBuf>,
131 {
132 Self {
133 include_dir: include_dir.into(),
134 lib_dir: lib_dir.into(),
135 }
136 }
137
138 pub fn include_dir(&self) -> &Path {
140 &self.include_dir
141 }
142
143 pub fn lib_dir(&self) -> &Path {
145 &self.lib_dir
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct Build {
152 enable_draft: bool,
153 build_debug: bool,
154 libsodium: Option<LibLocation>,
155}
156
157impl Build {
158 pub fn new() -> Self {
160 Self {
161 enable_draft: false,
162 build_debug: false,
163 libsodium: None,
164 }
165 }
166}
167
168impl Default for Build {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl Build {
175 pub fn build_debug(&mut self, enabled: bool) -> &mut Self {
177 self.build_debug = enabled;
178 self
179 }
180
181 pub fn enable_draft(&mut self, enabled: bool) -> &mut Self {
183 self.enable_draft = enabled;
184 self
185 }
186
187 pub fn with_libsodium(&mut self, maybe: Option<LibLocation>) -> &mut Self {
199 self.libsodium = maybe;
200 self
201 }
202
203 pub fn build(&mut self) {
208 let vendor = Path::new(env!("CARGO_MANIFEST_DIR")).join("vendor");
209
210 let mut build = cc::Build::new();
211 build
212 .cpp(true)
213 .define("ZMQ_BUILD_TESTS", "OFF")
214 .include(vendor.join("include"))
215 .include(vendor.join("src"));
216
217 add_cpp_sources(
218 &mut build,
219 vendor.join("src"),
220 &[
221 "address",
222 "channel",
223 "client",
224 "clock",
225 "ctx",
226 "curve_client",
227 "curve_mechanism_base",
228 "curve_server",
229 "dealer",
230 "decoder_allocators",
231 "devpoll",
232 "dgram",
233 "dish",
234 "dist",
235 "endpoint",
236 "epoll",
237 "err",
238 "fq",
239 "gather",
240 "gssapi_client",
241 "gssapi_mechanism_base",
242 "gssapi_server",
243 "io_object",
244 "io_thread",
245 "ip_resolver",
246 "ip",
247 "ipc_address",
248 "ipc_connecter",
249 "ipc_listener",
250 "kqueue",
251 "lb",
252 "mailbox_safe",
253 "mailbox",
254 "mechanism_base",
255 "mechanism",
256 "metadata",
257 "msg",
258 "mtrie",
259 "norm_engine",
260 "null_mechanism",
261 "object",
262 "options",
263 "own",
264 "pair",
265 "peer",
266 "pgm_receiver",
267 "pgm_sender",
268 "pgm_socket",
269 "pipe",
270 "plain_client",
271 "plain_server",
272 "poll",
273 "poller_base",
274 "polling_util",
275 "pollset",
276 "precompiled",
277 "proxy",
278 "pub",
279 "pull",
280 "push",
281 "radio",
282 "radix_tree",
283 "random",
284 "raw_decoder",
285 "raw_encoder",
286 "raw_engine",
287 "reaper",
288 "rep",
289 "req",
290 "router",
291 "scatter",
292 "select",
293 "server",
294 "session_base",
295 "signaler",
296 "socket_base",
297 "socket_poller",
298 "socks_connecter",
299 "socks",
300 "stream_connecter_base",
301 "stream_engine_base",
302 "stream_listener_base",
303 "stream",
304 "sub",
305 "tcp_address",
306 "tcp_connecter",
307 "tcp_listener",
308 "tcp",
309 "thread",
310 "timers",
311 "tipc_address",
312 "tipc_connecter",
313 "tipc_listener",
314 "trie",
315 "udp_address",
316 "udp_engine",
317 "v1_decoder",
318 "v1_encoder",
319 "v2_decoder",
320 "v2_encoder",
321 "v3_1_encoder",
322 "vmci_address",
323 "vmci_connecter",
324 "vmci_listener",
325 "vmci",
326 "ws_address",
327 "ws_connecter",
328 "ws_decoder",
329 "ws_encoder",
330 "ws_engine",
331 "ws_listener",
332 "xpub",
335 "xsub",
336 "zap_client",
337 "zmq_utils",
338 "zmq",
339 "zmtp_engine",
340 ],
341 );
342
343 add_c_sources(&mut build, vendor.join("external/sha1"), &["sha1.c"]);
344
345 if self.enable_draft {
346 build.define("ZMQ_BUILD_DRAFT_API", "1");
347 }
348
349 build.define("ZMQ_USE_CV_IMPL_STL11", "1");
350 build.define("ZMQ_STATIC", "1");
351 build.define("ZMQ_USE_BUILTIN_SHA1", "1");
352
353 build.define("ZMQ_HAVE_WS", "1");
354
355 let target = env::var("TARGET").unwrap();
356
357 if let Some(libsodium) = &self.libsodium {
358 build.define("ZMQ_USE_LIBSODIUM", "1");
359 build.define("ZMQ_HAVE_CURVE", "1");
360
361 build.include(libsodium.include_dir());
362 println!(
363 "cargo:rustc-link-search={}",
364 libsodium.lib_dir().display()
365 );
366
367 if target.contains("msvc") {
368 fs::copy(
369 libsodium
370 .include_dir()
371 .join("../../../builds/msvc/version.h"),
372 libsodium.include_dir().join("sodium/version.h"),
373 )
374 .unwrap();
375 }
376
377 if target.contains("msvc") {
378 println!("cargo:rustc-link-lib=static=libsodium");
379 } else {
380 println!("cargo:rustc-link-lib=static=sodium");
381 }
382 }
383
384 let create_platform_hpp_shim = |build: &mut cc::Build| {
385 let out_includes = PathBuf::from(std::env::var("OUT_DIR").unwrap());
392
393 let mut f =
396 File::create(out_includes.join("platform.hpp")).unwrap();
397 f.write_all(b"").unwrap();
398 f.sync_all().unwrap();
399
400 build.include(out_includes);
401 };
402
403 let mut has_strlcpy = false;
404 if target.contains("windows") {
405 add_c_sources(
407 &mut build,
408 vendor.join("external/wepoll"),
409 &["wepoll.c"],
410 );
411
412 build.define("ZMQ_IOTHREAD_POLLER_USE_EPOLL", "1");
413 build.define("ZMQ_POLL_BASED_ON_POLL", "1");
414 build.define("_WIN32_WINNT", "0x0600"); build.define("ZMQ_HAVE_STRUCT_SOCKADDR_UN", "1");
416
417 println!("cargo:rustc-link-lib=iphlpapi");
418
419 if target.contains("msvc") {
420 build.include(vendor.join("builds/deprecated-msvc"));
421 build.flag("/GL-");
424
425 build.flag("/EHsc");
428 } else {
429 create_platform_hpp_shim(&mut build);
430 build.define("HAVE_STRNLEN", "1");
431 }
432
433 if !target.contains("uwp") {
434 build.define("ZMQ_HAVE_IPC", "1");
435 }
436 } else if target.contains("linux") {
437 create_platform_hpp_shim(&mut build);
438 build.define("ZMQ_IOTHREAD_POLLER_USE_EPOLL", "1");
439 build.define("ZMQ_POLL_BASED_ON_POLL", "1");
440 build.define("ZMQ_HAVE_IPC", "1");
441
442 build.define("HAVE_STRNLEN", "1");
443 build.define("ZMQ_HAVE_UIO", "1");
444 build.define("ZMQ_HAVE_STRUCT_SOCKADDR_UN", "1");
445
446 if target.contains("android") {
447 has_strlcpy = true;
448 }
449
450 if target.contains("musl") {
451 has_strlcpy = true;
452 }
453 } else if target.contains("apple") || target.contains("freebsd") {
454 create_platform_hpp_shim(&mut build);
455 build.define("ZMQ_IOTHREAD_POLLER_USE_KQUEUE", "1");
456 build.define("ZMQ_POLL_BASED_ON_POLL", "1");
457 build.define("HAVE_STRNLEN", "1");
458 build.define("ZMQ_HAVE_UIO", "1");
459 build.define("ZMQ_HAVE_IPC", "1");
460 build.define("ZMQ_HAVE_STRUCT_SOCKADDR_UN", "1");
461 has_strlcpy = true;
462 }
463
464 if env::var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu"
466 && !has_strlcpy
467 && glibc::has_strlcpy()
468 {
469 has_strlcpy = true;
470 }
471
472 if has_strlcpy {
473 build.define("ZMQ_HAVE_STRLCPY", "1");
474 }
475
476 if !target.contains("msvc") {
478 if cxx11::has_cxx11() {
481 build.std("c++11");
482 }
483 }
484
485 let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
486 let lib_dir = out_dir.join("lib");
487
488 build.out_dir(&lib_dir);
489 build.compile("zmq");
490
491 if target.contains("msvc")
494 && rename_libzmq_in_dir(&lib_dir, "zmq.lib").is_err()
495 {
496 panic!("unable to find compiled `libzmq` lib");
497 }
498
499 let source_dir = out_dir.join("source");
500 let include_dir = source_dir.join("include");
501
502 dircpy::copy_dir(vendor.join("include"), &include_dir)
504 .expect("unable to copy include dir");
505 dircpy::copy_dir(vendor.join("src"), source_dir.join("src"))
506 .expect("unable to copy src dir");
507 dircpy::copy_dir(vendor.join("external"), source_dir.join("external"))
508 .expect("unable to copy external dir");
509
510 println!("cargo:rustc-link-search=native={}", lib_dir.display());
511 println!("cargo:rustc-link-lib=static=zmq");
512 println!("cargo:include={}", include_dir.display());
513 println!("cargo:lib={}", lib_dir.display());
514 println!("cargo:out={}", out_dir.display());
515 }
516}
517
518#[cfg(test)]
519mod test {
520 #[test]
521 fn version_works() {
522 let version = testcrate::version();
523 println!("{:?}", version);
524 assert_eq!(version, (4, 3, 5));
525 }
526
527 #[test]
528 fn sodium_version_works() {
529 let version = testcrate::sodium_version();
530 println!("{:?}", version.to_str().unwrap());
531 assert!(version.to_str().unwrap().starts_with("1.0"));
532 }
533}