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