1 use std::env;
2 use std::fs;
3 use std::path::PathBuf;
4 use std::process::Command;
5 
main()6 fn main() {
7     println!("cargo:rerun-if-env-changed=LIBZ_SYS_STATIC");
8     println!("cargo:rerun-if-changed=build.rs");
9     let host = env::var("HOST").unwrap();
10     let target = env::var("TARGET").unwrap();
11 
12     let host_and_target_contain = |s| host.contains(s) && target.contains(s);
13 
14     let want_ng = cfg!(feature = "zlib-ng") && !cfg!(feature = "stock-zlib");
15 
16     if want_ng && target != "wasm32-unknown-unknown" {
17         return build_zlib_ng(&target, true);
18     }
19 
20     // Don't run pkg-config if we're linking statically (we'll build below) and
21     // also don't run pkg-config on macOS/FreeBSD/DragonFly. That'll end up printing
22     // `-L /usr/lib` which wreaks havoc with linking to an OpenSSL in /usr/local/lib
23     // (Homebrew, Ports, etc.)
24     let want_static =
25         cfg!(feature = "static") || env::var("LIBZ_SYS_STATIC").unwrap_or(String::new()) == "1";
26     if !want_static &&
27        !target.contains("msvc") && // pkg-config just never works here
28        !(host_and_target_contain("apple") ||
29          host_and_target_contain("freebsd") ||
30          host_and_target_contain("dragonfly"))
31     {
32         // Don't print system lib dirs to cargo since this interferes with other
33         // packages adding non-system search paths to link against libraries
34         // that are also found in a system-wide lib dir.
35         let zlib = pkg_config::Config::new()
36             .cargo_metadata(true)
37             .print_system_libs(false)
38             .probe("zlib");
39         match zlib {
40             Ok(_) => return,
41             Err(e) => {
42                 println!("cargo-warning={}", e.to_string())
43             }
44         }
45     }
46 
47     if target.contains("msvc") {
48         if try_vcpkg() {
49             return;
50         }
51     }
52 
53     // All android compilers should come with libz by default, so let's just use
54     // the one already there. Likewise, Haiku always ships with libz, so we can
55     // link to it even when cross-compiling.
56     if target.contains("android") || target.contains("haiku") {
57         println!("cargo:rustc-link-lib=z");
58         return;
59     }
60 
61     let mut cfg = cc::Build::new();
62 
63     // Situations where we build unconditionally.
64     //
65     // MSVC basically never has it preinstalled, MinGW picks up a bunch of weird
66     // paths we don't like, `want_static` may force us, and cross compiling almost
67     // never has a prebuilt version.
68     //
69     // Apple platforms have libz.1.dylib, and it's usually available even when
70     // cross compiling (via fat binary or in the target's Xcode SDK)
71     let cross_compiling = target != host;
72     let apple_to_apple = host.contains("-apple-") && target.contains("-apple-");
73     if target.contains("msvc")
74         || target.contains("pc-windows-gnu")
75         || want_static
76         || (cross_compiling && !apple_to_apple)
77     {
78         return build_zlib(&mut cfg, &target);
79     }
80 
81     // If we've gotten this far we're probably a pretty standard platform.
82     // Almost all platforms here ship libz by default, but some don't have
83     // pkg-config files that we would find above.
84     //
85     // In any case test if zlib is actually installed and if so we link to it,
86     // otherwise continue below to build things.
87     if zlib_installed(&mut cfg) {
88         println!("cargo:rustc-link-lib=z");
89         return;
90     }
91 
92     build_zlib(&mut cfg, &target)
93 }
94 
build_zlib(cfg: &mut cc::Build, target: &str)95 fn build_zlib(cfg: &mut cc::Build, target: &str) {
96     let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
97     let lib = dst.join("lib");
98 
99     cfg.warnings(false).out_dir(&lib).include("src/zlib");
100 
101     cfg.file("src/zlib/adler32.c")
102         .file("src/zlib/compress.c")
103         .file("src/zlib/crc32.c")
104         .file("src/zlib/deflate.c")
105         .file("src/zlib/infback.c")
106         .file("src/zlib/inffast.c")
107         .file("src/zlib/inflate.c")
108         .file("src/zlib/inftrees.c")
109         .file("src/zlib/trees.c")
110         .file("src/zlib/uncompr.c")
111         .file("src/zlib/zutil.c");
112 
113     if !cfg!(feature = "libc") || target.starts_with("wasm32") {
114         cfg.define("Z_SOLO", None);
115     } else {
116         cfg.file("src/zlib/gzclose.c")
117             .file("src/zlib/gzlib.c")
118             .file("src/zlib/gzread.c")
119             .file("src/zlib/gzwrite.c");
120     }
121 
122     if !target.contains("windows") {
123         cfg.define("STDC", None);
124         cfg.define("_LARGEFILE64_SOURCE", None);
125         cfg.define("_POSIX_SOURCE", None);
126         cfg.flag("-fvisibility=hidden");
127     }
128     if target.contains("apple") {
129         cfg.define("_C99_SOURCE", None);
130     }
131     if target.contains("solaris") {
132         cfg.define("_XOPEN_SOURCE", "700");
133     }
134 
135     cfg.compile("z");
136 
137     fs::create_dir_all(dst.join("include")).unwrap();
138     fs::copy("src/zlib/zlib.h", dst.join("include/zlib.h")).unwrap();
139     fs::copy("src/zlib/zconf.h", dst.join("include/zconf.h")).unwrap();
140 
141     fs::create_dir_all(lib.join("pkgconfig")).unwrap();
142     fs::write(
143         lib.join("pkgconfig/zlib.pc"),
144         fs::read_to_string("src/zlib/zlib.pc.in")
145             .unwrap()
146             .replace("@prefix@", dst.to_str().unwrap()),
147     )
148     .unwrap();
149 
150     println!("cargo:root={}", dst.to_str().unwrap());
151     println!("cargo:rustc-link-search=native={}", lib.to_str().unwrap());
152     println!("cargo:include={}/include", dst.to_str().unwrap());
153 }
154 
155 #[cfg(not(feature = "zlib-ng"))]
build_zlib_ng(_target: &str, _compat: bool)156 fn build_zlib_ng(_target: &str, _compat: bool) {}
157 
158 #[cfg(feature = "zlib-ng")]
159 mod build_zng;
160 #[cfg(feature = "zlib-ng")]
161 use build_zng::build_zlib_ng;
162 
163 #[cfg(not(target_env = "msvc"))]
try_vcpkg() -> bool164 fn try_vcpkg() -> bool {
165     false
166 }
167 
168 #[cfg(target_env = "msvc")]
try_vcpkg() -> bool169 fn try_vcpkg() -> bool {
170     // see if there is a vcpkg tree with zlib installed
171     match vcpkg::Config::new()
172         .emit_includes(true)
173         .lib_names("zlib", "zlib1")
174         .probe("zlib")
175     {
176         Ok(_) => true,
177         Err(e) => {
178             println!("note, vcpkg did not find zlib: {}", e);
179             false
180         }
181     }
182 }
183 
zlib_installed(cfg: &mut cc::Build) -> bool184 fn zlib_installed(cfg: &mut cc::Build) -> bool {
185     let compiler = cfg.get_compiler();
186     let mut cmd = Command::new(compiler.path());
187     cmd.arg("src/smoke.c").arg("-o").arg("/dev/null").arg("-lz");
188 
189     println!("running {:?}", cmd);
190     if let Ok(status) = cmd.status() {
191         if status.success() {
192             return true;
193         }
194     }
195 
196     false
197 }
198