zeromq_src/
lib.rs

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    // Temporarily use c instead of c++.
30    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
41// Returns Ok(()) is file was renamed,
42// Returns Err(()) otherwise.
43fn 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    // Attempt to compile a c program that links to strlcpy from the std
69    // library to determine whether glibc packages it.
70    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    // Attempt to compile a c program that links to winsock2.h & aflinux.h
97    // library to determine whether windows has these header files.
98    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    // Attempt to compile a c program that links has the c++11 flag to determine
126    // whether it is supported.
127    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/// The location of a library.
151#[derive(Debug, Clone)]
152pub struct LibLocation {
153    include_dir: PathBuf,
154    lib_dir: PathBuf,
155}
156
157impl LibLocation {
158    /// Create a new `LibLocation`.
159    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    /// Returns the `include_dir`.
171    pub fn include_dir(&self) -> &Path {
172        &self.include_dir
173    }
174
175    /// Returns the `lib_dir`.
176    pub fn lib_dir(&self) -> &Path {
177        &self.lib_dir
178    }
179}
180
181/// Settings for building zmq.
182#[derive(Debug, Clone)]
183pub struct Build {
184    enable_draft: bool,
185    build_debug: bool,
186    libsodium: Option<LibLocation>,
187}
188
189impl Build {
190    /// Create a new build.
191    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    /// Build the debug version of the lib.
208    pub fn build_debug(&mut self, enabled: bool) -> &mut Self {
209        self.build_debug = enabled;
210        self
211    }
212
213    /// Enable the DRAFT API.
214    pub fn enable_draft(&mut self, enabled: bool) -> &mut Self {
215        self.enable_draft = enabled;
216        self
217    }
218
219    /// Enable the CURVE feature and link against an external `libsodium` library.
220    ///
221    /// Users can link against an installed lib or another `sys` or `src` crate
222    /// that provides the lib.
223    ///
224    /// Note that by default `libzmq` builds without `libsodium` by instead
225    /// relying on `tweetnacl`. However since this `tweetnacl` [has never been
226    /// audited nor is ready for production](https://github.com/zeromq/libzmq/issues/3006),
227    /// we require linking against `libsodium` to enable `ZMQ_CURVE`.
228    ///
229    /// [`links build metadata`]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
230    pub fn with_libsodium(&mut self, maybe: Option<LibLocation>) -> &mut Self {
231        self.libsodium = maybe;
232        self
233    }
234
235    /// Build and link the lib based on the provided options.
236    ///
237    /// Returns an `Artifacts` which contains metadata for linking
238    /// against the compiled lib from rust code.
239    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            // We use c++ as the default.
245            .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                // "wss_address", // requires gnutls
366                // "wss_engine", // requires gnutls
367                "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            // https://cmake.org/cmake/help/latest/command/configure_file.html
419            // TODO: Replace `#cmakedefine` with the appropriate `#define`
420            // let _platform_file =
421            //     fs::read_to_string(path.join("builds/cmake/platform.hpp.in"))
422            //         .unwrap();
423
424            let out_includes = PathBuf::from(std::env::var("OUT_DIR").unwrap());
425
426            // Write out an empty platform file: defines will be set through cc directly,
427            // sync to prevent potential IO troubles later on
428            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            // on windows vista and up we can use `epoll` through the `wepoll` lib
439            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"); // vista
451            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                // We need to explicitly disable `/GL` flag, otherwise
458                // we get linkage error.
459                build.flag("/GL-");
460
461                // Fix warning C4530: "C++ exception handler used, but unwind
462                // semantics are not enabled. Specify /EHsc"
463                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        // https://github.com/jean-airoldie/zeromq-src-rs/issues/28
502        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        // MSVC does not support c++11, since c++14 is the minimum.
514        if !target.contains("msvc") {
515            // Enable c++11 if possible to fix issue 45
516            // (https://github.com/jean-airoldie/zeromq-src-rs/issues/45).
517            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        // On windows we need to rename the static compiled lib
529        // since its name is unpredictable.
530        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        // Finally we need to copy the include files.
540        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}