1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::ffi::OsString;
5use std::path::{Path, PathBuf};
6use std::process::Output;
7use std::{env, io, thread};
8
9mod download;
10mod verifier;
11
12static NGINX_DEFAULT_SOURCE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/nginx");
13
14const NGINX_BUILD_INFO: &str = "last-build-info";
15const NGINX_BINARY: &str = "nginx";
16
17static NGINX_CONFIGURE_BASE: &[&str] = &[
18 "--with-compat",
19 "--with-http_realip_module",
20 "--with-http_ssl_module",
21 "--with-http_v2_module",
22 "--with-stream",
23 "--with-stream_realip_module",
24 "--with-stream_ssl_module",
25 "--with-threads",
26];
27
28const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 10] = [
29 "CACHE_DIR",
30 "CARGO_MANIFEST_DIR",
31 "CARGO_TARGET_TMPDIR",
32 "NGX_CONFIGURE_ARGS",
33 "NGX_CFLAGS",
34 "NGX_LDFLAGS",
35 "NGX_VERSION",
36 "OPENSSL_VERSION",
37 "PCRE2_VERSION",
38 "ZLIB_VERSION",
39];
40
41pub fn print_cargo_metadata() {
59 for file in ["lib.rs", "download.rs", "verifier.rs"] {
60 println!(
61 "cargo::rerun-if-changed={path}/src/{file}",
62 path = env!("CARGO_MANIFEST_DIR")
63 )
64 }
65
66 for var in ENV_VARS_TRIGGERING_RECOMPILE {
67 println!("cargo::rerun-if-env-changed={var}");
68 }
69}
70
71pub fn build(build_dir: impl AsRef<Path>) -> io::Result<(PathBuf, PathBuf)> {
73 let source_dir = PathBuf::from(NGINX_DEFAULT_SOURCE_DIR);
74 let build_dir = build_dir.as_ref().to_owned();
75
76 let (source_dir, vendored_flags) = download::prepare(&source_dir, &build_dir)?;
77
78 let flags = nginx_configure_flags(&vendored_flags);
79
80 configure(&source_dir, &build_dir, &flags)?;
81
82 make(&source_dir, &build_dir, ["build"])?;
83
84 Ok((source_dir, build_dir))
85}
86
87fn build_info(source_dir: &Path, configure_flags: &[String]) -> String {
89 format!("{:?}|{}", source_dir, configure_flags.join(" "))
92}
93
94fn nginx_configure_flags(vendored: &[String]) -> Vec<String> {
96 let mut nginx_opts: Vec<String> = NGINX_CONFIGURE_BASE
97 .iter()
98 .map(|x| String::from(*x))
99 .collect();
100
101 nginx_opts.extend(vendored.iter().map(Into::into));
102
103 if let Ok(extra_args) = env::var("NGX_CONFIGURE_ARGS") {
104 nginx_opts.extend(extra_args.split_whitespace().map(Into::into));
106 }
107
108 if let Ok(cflags) = env::var("NGX_CFLAGS") {
109 nginx_opts.push(format!("--with-cc-opt={cflags}"));
110 }
111
112 if let Ok(ldflags) = env::var("NGX_LDFLAGS") {
113 nginx_opts.push(format!("--with-ld-opt={ldflags}"));
114 }
115
116 nginx_opts
117}
118
119fn configure(source_dir: &Path, build_dir: &Path, flags: &[String]) -> io::Result<()> {
121 let build_info = build_info(source_dir, flags);
122
123 if build_dir.join("Makefile").is_file()
124 && build_dir.join(NGINX_BINARY).is_file()
125 && matches!(
126 std::fs::read_to_string(build_dir.join(NGINX_BUILD_INFO)).map(|x| x == build_info),
127 Ok(true)
128 )
129 {
130 println!("Build info unchanged, skipping configure");
131 return Ok(());
132 }
133
134 println!("Using NGINX source at {source_dir:?}");
135
136 let configure = ["configure", "auto/configure"]
137 .into_iter()
138 .map(|x| source_dir.join(x))
139 .find(|x| x.is_file())
140 .ok_or(io::ErrorKind::NotFound)?;
141
142 println!(
143 "Running NGINX configure script with flags: {:?}",
144 flags.join(" ")
145 );
146
147 let mut build_dir_arg: OsString = "--builddir=".into();
148 build_dir_arg.push(build_dir);
149
150 let mut flags: Vec<OsString> = flags.iter().map(|x| x.into()).collect();
151 flags.push(build_dir_arg);
152
153 let output = duct::cmd(configure, flags)
154 .dir(source_dir)
155 .stderr_to_stdout()
156 .run()?;
157
158 if !output.status.success() {
159 println!("configure failed with {:?}", output.status);
160 return Err(io::ErrorKind::Other.into());
161 }
162
163 let _ = std::fs::write(build_dir.join(NGINX_BUILD_INFO), build_info);
164
165 Ok(())
166}
167
168fn make<U>(source_dir: &Path, build_dir: &Path, extra_args: U) -> io::Result<Output>
170where
171 U: IntoIterator,
172 U::Item: Into<OsString>,
173{
174 let num_jobs = match env::var("NUM_JOBS") {
176 Ok(s) => s.parse::<usize>().ok(),
177 Err(_) => thread::available_parallelism().ok().map(|n| n.get()),
178 }
179 .unwrap_or(1);
180
181 let mut args = vec![
182 OsString::from("-f"),
183 build_dir.join("Makefile").into(),
184 OsString::from("-j"),
185 num_jobs.to_string().into(),
186 ];
187 args.extend(extra_args.into_iter().map(Into::into));
188
189 let inherited = env::var("MAKE");
194 let make_commands: &[&str] = match inherited {
195 Ok(ref x) => &[x.as_str(), "gmake", "make"],
196 _ => &["gmake", "make"],
197 };
198
199 for make in make_commands {
202 let result = duct::cmd(*make, &args)
206 .dir(source_dir)
207 .stderr_to_stdout()
208 .run();
209
210 match result {
211 Err(err) if err.kind() == io::ErrorKind::NotFound => {
212 eprintln!("make: command '{make}' not found");
213 continue;
214 }
215 Ok(out) if !out.status.success() => {
216 return Err(io::Error::other(format!(
217 "make: '{}' failed with {:?}",
218 make, out.status
219 )));
220 }
221 _ => return result,
222 }
223 }
224
225 Err(io::ErrorKind::NotFound.into())
226}