1 extern crate cc;
2 extern crate pkg_config;
3 #[cfg(target_env = "msvc")]
4 extern crate vcpkg;
5 
6 use std::env;
7 use std::fs;
8 use std::path::{Path, PathBuf};
9 use std::process::Command;
10 
main()11 fn main() {
12     let host = env::var("HOST").unwrap();
13     let target = env::var("TARGET").unwrap();
14     let windows = target.contains("windows");
15 
16     // This feature trumps all others, and is largely set by rustbuild to force
17     // usage of the system library to ensure that we're always building an
18     // ABI-compatible Cargo.
19     if cfg!(feature = "force-system-lib-on-osx") && target.contains("apple") {
20         return println!("cargo:rustc-flags=-l curl");
21     }
22 
23     // When cross-compiling for Haiku, use the system's default supplied
24     // libcurl (it supports http2). This is in the case where rustc and
25     // cargo are built for Haiku, which is done from a Linux host.
26     if host != target && target.contains("haiku") {
27         return println!("cargo:rustc-flags=-l curl");
28     }
29 
30     // If the static-curl feature is disabled, probe for a system-wide libcurl.
31     if !cfg!(feature = "static-curl") {
32         // OSX ships libcurl by default, so we just use that version
33         // so long as it has the right features enabled.
34         if target.contains("apple") && (!cfg!(feature = "http2") || curl_config_reports_http2()) {
35             return println!("cargo:rustc-flags=-l curl");
36         }
37 
38         // Next, fall back and try to use pkg-config if its available.
39         if windows {
40             if try_vcpkg() {
41                 return;
42             }
43         } else if try_pkg_config() {
44             return;
45         }
46     }
47 
48     if !Path::new("curl/.git").exists() {
49         let _ = Command::new("git")
50             .args(&["submodule", "update", "--init"])
51             .status();
52     }
53 
54     if target.contains("apple") {
55         // On (older) OSX we need to link against the clang runtime,
56         // which is hidden in some non-default path.
57         //
58         // More details at https://github.com/alexcrichton/curl-rust/issues/279.
59         if let Some(path) = macos_link_search_path() {
60             println!("cargo:rustc-link-lib=clang_rt.osx");
61             println!("cargo:rustc-link-search={}", path);
62         }
63     }
64 
65     let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
66     let include = dst.join("include");
67     let build = dst.join("build");
68     println!("cargo:root={}", dst.display());
69     println!("cargo:include={}", include.display());
70     println!("cargo:static=1");
71     fs::create_dir_all(include.join("curl")).unwrap();
72 
73     for header in [
74         "curl.h",
75         "curlver.h",
76         "easy.h",
77         "options.h",
78         "mprintf.h",
79         "multi.h",
80         "stdcheaders.h",
81         "system.h",
82         "urlapi.h",
83         "typecheck-gcc.h",
84     ]
85     .iter()
86     {
87         fs::copy(
88             format!("curl/include/curl/{}", header),
89             include.join("curl").join(header),
90         )
91         .unwrap();
92     }
93 
94     let pkgconfig = dst.join("lib/pkgconfig");
95     fs::create_dir_all(&pkgconfig).unwrap();
96     let contents = fs::read_to_string("curl/libcurl.pc.in").unwrap();
97     fs::write(
98         pkgconfig.join("libcurl.pc"),
99         contents
100             .replace("@prefix@", dst.to_str().unwrap())
101             .replace("@exec_prefix@", "")
102             .replace("@libdir@", dst.join("lib").to_str().unwrap())
103             .replace("@includedir@", include.to_str().unwrap())
104             .replace("@CPPFLAG_CURL_STATICLIB@", "-DCURL_STATICLIB")
105             .replace("@LIBCURL_LIBS@", "")
106             .replace("@SUPPORT_FEATURES@", "")
107             .replace("@SUPPORT_PROTOCOLS@", "")
108             .replace("@CURLVERSION@", "7.61.1"),
109     )
110     .unwrap();
111 
112     let mut cfg = cc::Build::new();
113     cfg.out_dir(&build)
114         .include("curl/lib")
115         .include("curl/include")
116         .define("BUILDING_LIBCURL", None)
117         .define("CURL_DISABLE_CRYPTO_AUTH", None)
118         .define("CURL_DISABLE_DICT", None)
119         .define("CURL_DISABLE_GOPHER", None)
120         .define("CURL_DISABLE_IMAP", None)
121         .define("CURL_DISABLE_LDAP", None)
122         .define("CURL_DISABLE_LDAPS", None)
123         .define("CURL_DISABLE_NTLM", None)
124         .define("CURL_DISABLE_POP3", None)
125         .define("CURL_DISABLE_RTSP", None)
126         .define("CURL_DISABLE_SMB", None)
127         .define("CURL_DISABLE_SMTP", None)
128         .define("CURL_DISABLE_TELNET", None)
129         .define("CURL_DISABLE_TFTP", None)
130         .define("CURL_STATICLIB", None)
131         .define("ENABLE_IPV6", None)
132         .define("HAVE_ASSERT_H", None)
133         .define("OS", "\"unknown\"") // TODO
134         .define("HAVE_ZLIB_H", None)
135         .define("HAVE_LIBZ", None)
136         .file("curl/lib/asyn-thread.c")
137         .file("curl/lib/altsvc.c")
138         .file("curl/lib/base64.c")
139         .file("curl/lib/conncache.c")
140         .file("curl/lib/connect.c")
141         .file("curl/lib/content_encoding.c")
142         .file("curl/lib/cookie.c")
143         .file("curl/lib/curl_addrinfo.c")
144         .file("curl/lib/curl_ctype.c")
145         .file("curl/lib/curl_get_line.c")
146         .file("curl/lib/curl_memrchr.c")
147         .file("curl/lib/curl_range.c")
148         .file("curl/lib/curl_threads.c")
149         .file("curl/lib/dotdot.c")
150         .file("curl/lib/doh.c")
151         .file("curl/lib/dynbuf.c")
152         .file("curl/lib/easy.c")
153         .file("curl/lib/escape.c")
154         .file("curl/lib/file.c")
155         .file("curl/lib/fileinfo.c")
156         .file("curl/lib/formdata.c")
157         .file("curl/lib/getenv.c")
158         .file("curl/lib/getinfo.c")
159         .file("curl/lib/hash.c")
160         .file("curl/lib/hostasyn.c")
161         .file("curl/lib/hostcheck.c")
162         .file("curl/lib/hostip.c")
163         .file("curl/lib/hostip6.c")
164         .file("curl/lib/http.c")
165         .file("curl/lib/http2.c")
166         .file("curl/lib/http_chunks.c")
167         .file("curl/lib/http_proxy.c")
168         .file("curl/lib/if2ip.c")
169         .file("curl/lib/inet_ntop.c")
170         .file("curl/lib/inet_pton.c")
171         .file("curl/lib/llist.c")
172         .file("curl/lib/mime.c")
173         .file("curl/lib/mprintf.c")
174         .file("curl/lib/mqtt.c")
175         .file("curl/lib/multi.c")
176         .file("curl/lib/netrc.c")
177         .file("curl/lib/nonblock.c")
178         .file("curl/lib/parsedate.c")
179         .file("curl/lib/progress.c")
180         .file("curl/lib/rand.c")
181         .file("curl/lib/rename.c")
182         .file("curl/lib/select.c")
183         .file("curl/lib/sendf.c")
184         .file("curl/lib/setopt.c")
185         .file("curl/lib/share.c")
186         .file("curl/lib/slist.c")
187         .file("curl/lib/socks.c")
188         .file("curl/lib/socketpair.c")
189         .file("curl/lib/speedcheck.c")
190         .file("curl/lib/splay.c")
191         .file("curl/lib/strcase.c")
192         .file("curl/lib/strdup.c")
193         .file("curl/lib/strerror.c")
194         .file("curl/lib/strtok.c")
195         .file("curl/lib/strtoofft.c")
196         .file("curl/lib/timeval.c")
197         .file("curl/lib/transfer.c")
198         .file("curl/lib/url.c")
199         .file("curl/lib/urlapi.c")
200         .file("curl/lib/version.c")
201         .file("curl/lib/vtls/keylog.c")
202         .file("curl/lib/vtls/vtls.c")
203         .file("curl/lib/warnless.c")
204         .file("curl/lib/wildcard.c")
205         .define("HAVE_GETADDRINFO", None)
206         .define("HAVE_GETPEERNAME", None)
207         .define("HAVE_GETSOCKNAME", None)
208         .warnings(false);
209 
210     if cfg!(feature = "protocol-ftp") {
211         cfg.file("curl/lib/curl_fnmatch.c")
212             .file("curl/lib/ftp.c")
213             .file("curl/lib/ftplistparser.c")
214             .file("curl/lib/pingpong.c");
215     } else {
216         cfg.define("CURL_DISABLE_FTP", None);
217     }
218 
219     if cfg!(feature = "http2") {
220         cfg.define("USE_NGHTTP2", None)
221             .define("NGHTTP2_STATICLIB", None);
222 
223         println!("cargo:rustc-cfg=link_libnghttp2");
224         if let Some(path) = env::var_os("DEP_NGHTTP2_ROOT") {
225             let path = PathBuf::from(path);
226             cfg.include(path.join("include"));
227         }
228     }
229 
230     println!("cargo:rustc-cfg=link_libz");
231     if let Some(path) = env::var_os("DEP_Z_INCLUDE") {
232         cfg.include(path);
233     }
234 
235     if cfg!(feature = "spnego") {
236         cfg.define("USE_SPNEGO", None)
237             .file("curl/lib/http_negotiate.c")
238             .file("curl/lib/vauth/vauth.c");
239     }
240 
241     // Configure TLS backend. Since Cargo does not support mutually exclusive
242     // features, make sure we only compile one vtls.
243     if cfg!(feature = "mesalink") {
244         cfg.define("USE_MESALINK", None)
245             .file("curl/lib/vtls/mesalink.c");
246 
247         if let Some(path) = env::var_os("DEP_MESALINK_INCLUDE") {
248             cfg.include(path);
249         }
250 
251         if windows {
252             cfg.define("HAVE_WINDOWS", None);
253         } else {
254             cfg.define("HAVE_UNIX", None);
255         }
256     } else if cfg!(feature = "ssl") {
257         if windows {
258             cfg.define("USE_WINDOWS_SSPI", None)
259                 .define("USE_SCHANNEL", None)
260                 .file("curl/lib/x509asn1.c")
261                 .file("curl/lib/curl_sspi.c")
262                 .file("curl/lib/socks_sspi.c")
263                 .file("curl/lib/vtls/schannel.c")
264                 .file("curl/lib/vtls/schannel_verify.c");
265         } else if target.contains("-apple-") {
266             cfg.define("USE_SECTRANSP", None)
267                 .file("curl/lib/vtls/sectransp.c");
268             if xcode_major_version().map_or(true, |v| v >= 9) {
269                 // On earlier Xcode versions (<9), defining HAVE_BUILTIN_AVAILABLE
270                 // would cause __bultin_available() to fail to compile due to
271                 // unrecognized platform names, so we try to check for Xcode
272                 // version first (if unknown, assume it's recent, as in >= 9).
273                 cfg.define("HAVE_BUILTIN_AVAILABLE", "1");
274             }
275         } else {
276             cfg.define("USE_OPENSSL", None)
277                 .file("curl/lib/vtls/openssl.c");
278 
279             println!("cargo:rustc-cfg=link_openssl");
280             if let Some(path) = env::var_os("DEP_OPENSSL_INCLUDE") {
281                 cfg.include(path);
282             }
283         }
284     }
285 
286     // Configure platform-specific details.
287     if windows {
288         cfg.define("WIN32", None)
289             .define("USE_THREADS_WIN32", None)
290             .define("HAVE_IOCTLSOCKET_FIONBIO", None)
291             .define("USE_WINSOCK", None)
292             .file("curl/lib/system_win32.c")
293             .file("curl/lib/version_win32.c")
294             .file("curl/lib/curl_multibyte.c");
295 
296         if cfg!(feature = "spnego") {
297             cfg.file("curl/lib/vauth/spnego_sspi.c");
298         }
299     } else {
300         cfg.define("RECV_TYPE_ARG1", "int")
301             .define("HAVE_PTHREAD_H", None)
302             .define("HAVE_ARPA_INET_H", None)
303             .define("HAVE_ERRNO_H", None)
304             .define("HAVE_FCNTL_H", None)
305             .define("HAVE_NETDB_H", None)
306             .define("HAVE_NETINET_IN_H", None)
307             .define("HAVE_NETINET_TCP_H", None)
308             .define("HAVE_POLL_FINE", None)
309             .define("HAVE_POLL_H", None)
310             .define("HAVE_FCNTL_O_NONBLOCK", None)
311             .define("HAVE_SYS_SELECT_H", None)
312             .define("HAVE_SYS_STAT_H", None)
313             .define("HAVE_UNISTD_H", None)
314             .define("HAVE_RECV", None)
315             .define("HAVE_SELECT", None)
316             .define("HAVE_SEND", None)
317             .define("HAVE_SOCKET", None)
318             .define("HAVE_STERRROR_R", None)
319             .define("HAVE_SOCKETPAIR", None)
320             .define("HAVE_STRUCT_TIMEVAL", None)
321             .define("HAVE_SYS_UN_H", None)
322             .define("USE_THREADS_POSIX", None)
323             .define("USE_UNIX_SOCKETS", None)
324             .define("RECV_TYPE_ARG2", "void*")
325             .define("RECV_TYPE_ARG3", "size_t")
326             .define("RECV_TYPE_ARG4", "int")
327             .define("RECV_TYPE_RETV", "ssize_t")
328             .define("SEND_QUAL_ARG2", "const")
329             .define("SEND_TYPE_ARG1", "int")
330             .define("SEND_TYPE_ARG2", "void*")
331             .define("SEND_TYPE_ARG3", "size_t")
332             .define("SEND_TYPE_ARG4", "int")
333             .define("SEND_TYPE_RETV", "ssize_t")
334             .define("SIZEOF_CURL_OFF_T", "8")
335             .define("SIZEOF_INT", "4")
336             .define("SIZEOF_SHORT", "2");
337 
338         if target.contains("-apple-") {
339             cfg.define("__APPLE__", None)
340                 .define("macintosh", None)
341                 .define("HAVE_MACH_ABSOLUTE_TIME", None);
342         } else {
343             cfg.define("HAVE_CLOCK_GETTIME_MONOTONIC", None)
344                 .define("HAVE_GETTIMEOFDAY", None);
345         }
346 
347         if cfg!(feature = "spnego") {
348             cfg.define("HAVE_GSSAPI", None)
349                 .file("curl/lib/curl_gssapi.c")
350                 .file("curl/lib/socks_gssapi.c")
351                 .file("curl/lib/vauth/spnego_gssapi.c");
352             if let Some(path) = env::var_os("GSSAPI_ROOT") {
353                 let path = PathBuf::from(path);
354                 cfg.include(path.join("include"));
355             }
356 
357             // Link against the MIT gssapi library. It might be desirable to add support for
358             // choosing between MIT and Heimdal libraries in the future.
359             println!("cargo:rustc-link-lib=gssapi_krb5");
360         }
361 
362         let width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
363             .unwrap()
364             .parse::<usize>()
365             .unwrap();
366         cfg.define("SIZEOF_SSIZE_T", Some(&(width / 8).to_string()[..]));
367         cfg.define("SIZEOF_SIZE_T", Some(&(width / 8).to_string()[..]));
368         cfg.define("SIZEOF_LONG", Some(&(width / 8).to_string()[..]));
369 
370         cfg.flag("-fvisibility=hidden");
371     }
372 
373     cfg.compile("curl");
374 
375     if windows {
376         println!("cargo:rustc-link-lib=ws2_32");
377         println!("cargo:rustc-link-lib=crypt32");
378     }
379 
380     // Illumos/Solaris requires explicit linking with libnsl
381     if target.contains("solaris") {
382         println!("cargo:rustc-link-lib=nsl");
383     }
384 
385     if target.contains("-apple-") {
386         println!("cargo:rustc-link-lib=framework=Security");
387         println!("cargo:rustc-link-lib=framework=CoreFoundation");
388     }
389 }
390 
391 #[cfg(not(target_env = "msvc"))]
try_vcpkg() -> bool392 fn try_vcpkg() -> bool {
393     false
394 }
395 
396 #[cfg(target_env = "msvc")]
try_vcpkg() -> bool397 fn try_vcpkg() -> bool {
398     // the import library for the dll is called libcurl_imp
399     let mut successful_probe_details = match vcpkg::Config::new()
400         .lib_names("libcurl_imp", "libcurl")
401         .emit_includes(true)
402         .probe("curl")
403     {
404         Ok(details) => Some(details),
405         Err(e) => {
406             println!("first run of vcpkg did not find libcurl: {}", e);
407             None
408         }
409     };
410 
411     if successful_probe_details.is_none() {
412         match vcpkg::Config::new()
413             .lib_name("libcurl")
414             .emit_includes(true)
415             .probe("curl")
416         {
417             Ok(details) => successful_probe_details = Some(details),
418             Err(e) => println!("second run of vcpkg did not find libcurl: {}", e),
419         }
420     }
421 
422     if successful_probe_details.is_some() {
423         // Found libcurl which depends on openssl, libssh2 and zlib
424         // in the a default vcpkg installation. Probe for them
425         // but do not fail if they are not present as we may be working
426         // with a customized vcpkg installation.
427         vcpkg::Config::new()
428             .lib_name("libeay32")
429             .lib_name("ssleay32")
430             .probe("openssl")
431             .ok();
432 
433         vcpkg::probe_package("libssh2").ok();
434 
435         vcpkg::Config::new()
436             .lib_names("zlib", "zlib1")
437             .probe("zlib")
438             .ok();
439 
440         println!("cargo:rustc-link-lib=crypt32");
441         println!("cargo:rustc-link-lib=gdi32");
442         println!("cargo:rustc-link-lib=user32");
443         println!("cargo:rustc-link-lib=wldap32");
444         return true;
445     }
446     false
447 }
448 
try_pkg_config() -> bool449 fn try_pkg_config() -> bool {
450     let mut cfg = pkg_config::Config::new();
451     cfg.cargo_metadata(false);
452     let lib = match cfg.probe("libcurl") {
453         Ok(lib) => lib,
454         Err(e) => {
455             println!(
456                 "Couldn't find libcurl from pkgconfig ({:?}), \
457                  compiling it from source...",
458                 e
459             );
460             return false;
461         }
462     };
463 
464     // Not all system builds of libcurl have http2 features enabled, so if we've
465     // got a http2-requested build then we may fall back to a build from source.
466     if cfg!(feature = "http2") && !curl_config_reports_http2() {
467         return false;
468     }
469 
470     // Re-find the library to print cargo's metadata, then print some extra
471     // metadata as well.
472     cfg.cargo_metadata(true).probe("libcurl").unwrap();
473     for path in lib.include_paths.iter() {
474         println!("cargo:include={}", path.display());
475     }
476     true
477 }
478 
xcode_major_version() -> Option<u8>479 fn xcode_major_version() -> Option<u8> {
480     let output = Command::new("xcodebuild").arg("-version").output().ok()?;
481     if output.status.success() {
482         let stdout = String::from_utf8_lossy(&output.stdout);
483         println!("xcode version: {}", stdout);
484         let mut words = stdout.split_whitespace();
485         if words.next()? == "Xcode" {
486             let version = words.next()?;
487             return version[..version.find('.')?].parse().ok();
488         }
489     }
490     println!("unable to determine Xcode version, assuming >= 9");
491     None
492 }
493 
curl_config_reports_http2() -> bool494 fn curl_config_reports_http2() -> bool {
495     let output = Command::new("curl-config").arg("--features").output();
496     let output = match output {
497         Ok(out) => out,
498         Err(e) => {
499             println!("failed to run curl-config ({}), building from source", e);
500             return false;
501         }
502     };
503     if !output.status.success() {
504         println!("curl-config failed: {}", output.status);
505         return false;
506     }
507     let stdout = String::from_utf8_lossy(&output.stdout);
508     if !stdout.contains("HTTP2") {
509         println!(
510             "failed to find http-2 feature enabled in pkg-config-found \
511              libcurl, building from source"
512         );
513         return false;
514     }
515 
516     true
517 }
518 
macos_link_search_path() -> Option<String>519 fn macos_link_search_path() -> Option<String> {
520     let output = Command::new("clang")
521         .arg("--print-search-dirs")
522         .output()
523         .ok()?;
524     if !output.status.success() {
525         println!(
526             "failed to run 'clang --print-search-dirs', continuing without a link search path"
527         );
528         return None;
529     }
530 
531     let stdout = String::from_utf8_lossy(&output.stdout);
532     for line in stdout.lines() {
533         if line.contains("libraries: =") {
534             let path = line.split('=').skip(1).next()?;
535             return Some(format!("{}/lib/darwin", path));
536         }
537     }
538 
539     println!("failed to determine link search path, continuing without it");
540     None
541 }
542