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    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
38// Returns Ok(()) is file was renamed,
39// Returns Err(()) otherwise.
40fn 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    // Attempt to compile a c program that links to strlcpy from the std
66    // library to determine whether glibc packages it.
67    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    // Attempt to compile a c program that links has the c++11 flag to determine
94    // whether it is supported.
95    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/// The location of a library.
119#[derive(Debug, Clone)]
120pub struct LibLocation {
121    include_dir: PathBuf,
122    lib_dir: PathBuf,
123}
124
125impl LibLocation {
126    /// Create a new `LibLocation`.
127    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    /// Returns the `include_dir`.
139    pub fn include_dir(&self) -> &Path {
140        &self.include_dir
141    }
142
143    /// Returns the `lib_dir`.
144    pub fn lib_dir(&self) -> &Path {
145        &self.lib_dir
146    }
147}
148
149/// Settings for building zmq.
150#[derive(Debug, Clone)]
151pub struct Build {
152    enable_draft: bool,
153    build_debug: bool,
154    libsodium: Option<LibLocation>,
155}
156
157impl Build {
158    /// Create a new build.
159    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    /// Build the debug version of the lib.
176    pub fn build_debug(&mut self, enabled: bool) -> &mut Self {
177        self.build_debug = enabled;
178        self
179    }
180
181    /// Enable the DRAFT API.
182    pub fn enable_draft(&mut self, enabled: bool) -> &mut Self {
183        self.enable_draft = enabled;
184        self
185    }
186
187    /// Enable the CURVE feature and link against an external `libsodium` library.
188    ///
189    /// Users can link against an installed lib or another `sys` or `src` crate
190    /// that provides the lib.
191    ///
192    /// Note that by default `libzmq` builds without `libsodium` by instead
193    /// relying on `tweetnacl`. However since this `tweetnacl` [has never been
194    /// audited nor is ready for production](https://github.com/zeromq/libzmq/issues/3006),
195    /// we require linking against `libsodium` to enable `ZMQ_CURVE`.
196    ///
197    /// [`links build metadata`]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
198    pub fn with_libsodium(&mut self, maybe: Option<LibLocation>) -> &mut Self {
199        self.libsodium = maybe;
200        self
201    }
202
203    /// Build and link the lib based on the provided options.
204    ///
205    /// Returns an `Artifacts` which contains metadata for linking
206    /// against the compiled lib from rust code.
207    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                // "wss_address", // requires gnutls
333                // "wss_engine", // requires gnutls
334                "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            // https://cmake.org/cmake/help/latest/command/configure_file.html
386            // TODO: Replace `#cmakedefine` with the appropriate `#define`
387            // let _platform_file =
388            //     fs::read_to_string(path.join("builds/cmake/platform.hpp.in"))
389            //         .unwrap();
390
391            let out_includes = PathBuf::from(std::env::var("OUT_DIR").unwrap());
392
393            // Write out an empty platform file: defines will be set through cc directly,
394            // sync to prevent potential IO troubles later on
395            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            // on windows vista and up we can use `epoll` through the `wepoll` lib
406            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"); // vista
415            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                // We need to explicitly disable `/GL` flag, otherwise
422                // we get linkage error.
423                build.flag("/GL-");
424
425                // Fix warning C4530: "C++ exception handler used, but unwind
426                // semantics are not enabled. Specify /EHsc"
427                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        // https://github.com/jean-airoldie/zeromq-src-rs/issues/28
465        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        // MSVC does not support c++11, since c++14 is the minimum.
477        if !target.contains("msvc") {
478            // Enable c++11 if possible to fix issue 45
479            // (https://github.com/jean-airoldie/zeromq-src-rs/issues/45).
480            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        // On windows we need to rename the static compiled lib
492        // since its name is unpredictable.
493        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        // Finally we need to copy the include files.
503        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}