Skip to content

Use cc crate to build zlib #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 13, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 61 additions & 220 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,8 @@ extern crate vcpkg;
extern crate cc;

use std::env;
use std::ffi::OsString;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;

macro_rules! t {
($e:expr) => (match $e {
Ok(n) => n,
Err(e) => panic!("\n{} failed with {}\n", stringify!($e), e),
})
}
use std::fs;
use std::path::PathBuf;

fn main() {
println!("cargo:rerun-if-env-changed=LIBZ_SYS_STATIC");
Expand All @@ -40,233 +29,85 @@ fn main() {
return
}

// Practically all platforms come with libz installed already, but MSVC is
// one of those sole platforms that doesn't!
if target.contains("msvc") {
if try_vcpkg() {
return;
}

build_msvc_zlib(&target);
} else if target.contains("pc-windows-gnu") {
build_zlib_mingw();
} else if (target.contains("musl") ||
target != host ||
want_static) &&
!target.contains("windows-gnu") &&
!target.contains("android") {
build_zlib(&host, &target);
} else {
println!("cargo:rustc-link-lib=z");
}
}

fn build_zlib(host: &str, target: &str) {
let src = env::current_dir().unwrap().join("src/zlib");
let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let build = dst.join("build");
t!(fs::create_dir_all(&build));
cp_r(&src, &build);

run(configure(host, target)
.current_dir(&build)
.arg(format!("--prefix={}", dst.display())), "sh");
run(make()
.current_dir(&build)
.arg("libz.a"), "make");

t!(fs::create_dir_all(dst.join("lib/pkgconfig")));
t!(fs::create_dir_all(dst.join("include")));
t!(fs::copy(build.join("libz.a"), dst.join("lib/libz.a")));
t!(fs::copy(build.join("zlib.h"), dst.join("include/zlib.h")));
t!(fs::copy(build.join("zconf.h"), dst.join("include/zconf.h")));
t!(fs::copy(build.join("zlib.pc"), dst.join("lib/pkgconfig/zlib.pc")));

println!("cargo:rustc-link-lib=static=z");
println!("cargo:rustc-link-search={}/lib", dst.to_string_lossy());
println!("cargo:root={}", dst.to_string_lossy());
println!("cargo:include={}/include", dst.to_string_lossy());
}

fn configure(host: &str, target: &str) -> Command {
let compiler = cc::Build::new().get_compiler();
let mut cflags = OsString::new();
for arg in compiler.args() {
cflags.push(arg);
cflags.push(" ");
}

let mut cmd = Command::new("./configure");
cmd.env("CC", compiler.path());
cmd.env("CFLAGS", cflags);

if host != target {
cmd.env("CHOST", target);
// All android compilers should come with libz by default, so let's just use
// the one already there.
if target.contains("android") {
println!("cargo:rustc-link-lib=z");
return
}

return cmd
}

fn make() -> Command {
let cmd = if cfg!(any(target_os = "freebsd", target_os = "dragonfly")) {"gmake"} else {"make"};
let mut cmd = Command::new(cmd);

// We're using the MSYS make which doesn't work with the mingw32-make-style
// MAKEFLAGS, so remove that from the env if present.
if cfg!(windows) {
cmd.env_remove("MAKEFLAGS").env_remove("MFLAGS");
} else if let Some(makeflags) = env::var_os("CARGO_MAKEFLAGS") {
cmd.env("MAKEFLAGS", makeflags);
// Whitelist a bunch of situations where we build unconditionally.
//
// MSVC basically never has it preinstalled, MinGW picks up a bunch of weird
// paths we don't like, `want_static` may force us, cross compiling almost
// never has a prebuilt version, and musl is almost always static.
if target.contains("msvc") ||
target.contains("pc-windows-gnu") ||
want_static ||
target != host ||
target.contains("musl")
{
return build_zlib(&target);
}

return cmd
// If we've gotten this far we're probably a pretty standard platform.
// Almost all platforms here ship libz by default, so just assume it exists.
// This is buggy if zlib1g-dev isn't installed on Linux, we should fix that.
println!("cargo:rustc-link-lib=z");
}

// We have to run a few shell scripts, which choke quite a bit on both `\`
// characters and on `C:\` paths, so normalize both of them away.
fn sanitize_sh(path: &Path) -> String {
let path = path.to_str().unwrap().replace("\\", "/");
return change_drive(&path).unwrap_or(path);

fn change_drive(s: &str) -> Option<String> {
let mut ch = s.chars();
let drive = ch.next().unwrap_or('C');
if ch.next() != Some(':') {
return None
}
if ch.next() != Some('/') {
return None
}
Some(format!("/{}/{}", drive, &s[drive.len_utf8() + 2..]))
}
}

fn build_zlib_mingw() {
let src = env::current_dir().unwrap().join("src/zlib");
fn build_zlib(target: &str) {
let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let build = dst.join("build");
t!(fs::create_dir_all(&build));
cp_r(&src, &build);
let compiler = cc::Build::new().get_compiler();
let mut cflags = OsString::new();
for arg in compiler.args() {
cflags.push(arg);
cflags.push(" ");
}
let gcc = sanitize_sh(compiler.path());
let mut cmd = make();
cmd.arg("-f").arg("win32/Makefile.gcc")
.current_dir(&build)
.arg("install")
.arg(format!("prefix={}", sanitize_sh(&dst)))
.arg("IMPLIB=")
.arg(format!("INCLUDE_PATH={}", sanitize_sh(&dst.join("include"))))
.arg(format!("LIBRARY_PATH={}", sanitize_sh(&dst.join("lib"))))
.arg(format!("BINARY_PATH={}", sanitize_sh(&dst.join("bin"))))
.env("CFLAGS", cflags);

if gcc != "gcc" {
match gcc.find("gcc") {
Some(0) => {}
Some(i) => {
cmd.arg(format!("PREFIX={}", &gcc[..i]));
}
None => {}
}
}
run(&mut cmd, "make");

t!(fs::create_dir_all(dst.join("lib/pkgconfig")));

println!("cargo:rustc-link-lib=static=z");
println!("cargo:rustc-link-search={}/lib", dst.to_string_lossy());
println!("cargo:root={}", dst.to_string_lossy());
println!("cargo:include={}/include", dst.to_string_lossy());
}

fn cp_r(dir: &Path, dst: &Path) {
for entry in t!(fs::read_dir(dir)) {
let entry = t!(entry);
let path = entry.path();
let dst = dst.join(path.file_name().unwrap());
if t!(fs::metadata(&path)).is_file() {
t!(fs::copy(path, dst));
} else {
t!(fs::create_dir_all(&dst));
cp_r(&path, &dst);
}
}
}

fn build_msvc_zlib(target: &str) {
let src = t!(env::current_dir()).join("src/zlib");
let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());

t!(fs::create_dir_all(dst.join("lib")));
t!(fs::create_dir_all(dst.join("include")));
t!(fs::create_dir_all(dst.join("build")));
cp_r(&src, &dst.join("build"));

let features = env::var("CARGO_CFG_TARGET_FEATURE")
.unwrap_or(String::new());
if features.contains("crt-static") {
let mut makefile = String::new();
let makefile_path = dst.join("build/win32/Makefile.msc");
t!(t!(File::open(&makefile_path)).read_to_string(&mut makefile));
let new_makefile = makefile.replace(" -MD ", " -MT ");
t!(t!(File::create(&makefile_path)).write_all(new_makefile.as_bytes()));
let mut cfg = cc::Build::new();
cfg.warnings(false)
.out_dir(&build)
.include("src/zlib");

cfg.file("src/zlib/adler32.c")
.file("src/zlib/compress.c")
.file("src/zlib/crc32.c")
.file("src/zlib/deflate.c")
.file("src/zlib/gzclose.c")
.file("src/zlib/gzlib.c")
.file("src/zlib/gzread.c")
.file("src/zlib/gzwrite.c")
.file("src/zlib/infback.c")
.file("src/zlib/inffast.c")
.file("src/zlib/inflate.c")
.file("src/zlib/inftrees.c")
.file("src/zlib/trees.c")
.file("src/zlib/uncompr.c")
.file("src/zlib/zutil.c");
if !target.contains("windows") {
cfg.define("STDC", None);
cfg.define("_LARGEFILE64_SOURCE", None);
cfg.define("_POSIX_SOURCE", None);
}

let nmake = cc::windows_registry::find(target, "nmake.exe");
let mut nmake = nmake.unwrap_or(Command::new("nmake.exe"));

// These env vars are intended for mingw32-make, not `namek`, which chokes
// on them anyway.
nmake.env_remove("MAKEFLAGS")
.env_remove("MFLAGS");

run(nmake.current_dir(dst.join("build"))
.arg("/nologo")
.arg("/f")
.arg(dst.join("build/win32/Makefile.msc"))
.arg("zlib.lib"), "nmake.exe");

for file in t!(fs::read_dir(&dst.join("build"))) {
let file = t!(file).path();
if let Some(s) = file.file_name().and_then(|s| s.to_str()) {
if s.ends_with(".h") {
t!(fs::copy(&file, dst.join("include").join(s)));
}
}
}
t!(fs::copy(dst.join("build/zlib.lib"), dst.join("lib/zlib.lib")));
cfg.compile("z");

println!("cargo:rustc-link-lib=static=zlib");
println!("cargo:rustc-link-search={}/lib", dst.to_string_lossy());
println!("cargo:root={}", dst.to_string_lossy());
println!("cargo:include={}/include", dst.to_string_lossy());
}
fs::create_dir_all(dst.join("lib/pkgconfig")).unwrap();
fs::create_dir_all(dst.join("include")).unwrap();
fs::copy("src/zlib/zlib.h", dst.join("include/zlib.h")).unwrap();
fs::copy("src/zlib/zconf.h", dst.join("include/zconf.h")).unwrap();

fn run(cmd: &mut Command, program: &str) {
println!("running: {:?}", cmd);
let status = match cmd.status() {
Ok(status) => status,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
fail(&format!("failed to execute command: {}\nIs `{}` \
not installed?",
e,
program));
}
Err(e) => fail(&format!("failed to execute command: {}", e)),
};
if !status.success() {
fail(&format!("command did not execute successfully, got: {}", status));
}
}
fs::write(
dst.join("lib/pkgconfig/zlib.pc"),
fs::read_to_string("src/zlib/zlib.pc.in")
.unwrap()
.replace("@prefix@", dst.to_str().unwrap()),
).unwrap();

fn fail(s: &str) -> ! {
println!("\n\n{}\n\n", s);
std::process::exit(1);
println!("cargo:root={}", dst.to_str().unwrap());
println!("cargo:include={}/include", dst.to_str().unwrap());
}

#[cfg(not(target_env = "msvc"))]
Expand Down