Skip to content

Commit e3bb1b4

Browse files
committed
feat(sys): export detected nginx build configuration
1 parent 4e833b2 commit e3bb1b4

File tree

3 files changed

+157
-12
lines changed

3 files changed

+157
-12
lines changed

Cargo.lock

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nginx-sys/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ categories = ["external-ffi-bindings"]
55
description = "FFI bindings to NGINX"
66
keywords = ["nginx", "ffi", "sys"]
77
build = "build/main.rs"
8+
# This field is required to export DEP_NGINX_ vars
9+
# See https://github.com/rust-lang/cargo/issues/3544
10+
links = "nginx"
811
edition.workspace = true
912
license.workspace = true
1013
homepage.workspace = true
@@ -15,8 +18,10 @@ rust-version.workspace = true
1518

1619
[build-dependencies]
1720
bindgen = "0.70.1"
21+
cc = "1.2.0"
1822
duct = { version = "0.13.7", optional = true }
1923
flate2 = { version = "1.0.28", optional = true }
24+
regex = "1.11.1"
2025
tar = { version = "0.4.40", optional = true }
2126
ureq = { version = "2.9.6", features = ["tls"], optional = true }
2227
which = { version = "6.0.0", optional = true }

nginx-sys/build/main.rs

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,57 @@ extern crate bindgen;
22

33
use std::env;
44
use std::error::Error as StdError;
5-
use std::fs::read_to_string;
6-
use std::path::PathBuf;
5+
use std::fs::{read_to_string, File};
6+
use std::io::Write;
7+
use std::path::{Path, PathBuf};
78

89
#[cfg(feature = "vendored")]
910
mod vendored;
1011

1112
const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 2] = ["OUT_DIR", "NGX_OBJS"];
1213

14+
/// The feature flags set by the nginx configuration script.
15+
///
16+
/// This list is a subset of NGX_/NGX_HAVE_ macros known to affect the structure layout or module
17+
/// avialiability.
18+
///
19+
/// The flags will be exposed to the buildscripts of _direct_ dependendents of this crate as
20+
/// `DEP_NGINX_FEATURES` environment variable.
21+
/// The list of recognized values will be exported as `DEP_NGINX_FEATURES_CHECK`.
22+
const NGX_CONF_FEATURES: &[&str] = &[
23+
"compat",
24+
"debug",
25+
"have_epollrdhup",
26+
"have_file_aio",
27+
"have_kqueue",
28+
"http_cache",
29+
"http_dav",
30+
"http_gzip",
31+
"http_realip",
32+
"http_ssi",
33+
"http_ssl",
34+
"http_upstream_zone",
35+
"http_v2",
36+
"http_v3",
37+
"http_x_forwarded_for",
38+
"pcre",
39+
"pcre2",
40+
"quic",
41+
"ssl",
42+
"stream_ssl",
43+
"stream_upstream_zone",
44+
"threads",
45+
];
46+
47+
/// The operating systems supported by the nginx configuration script
48+
///
49+
/// The detected value will be exposed to the buildsrcipts of _direct_ dependents of this crate as
50+
/// `DEP_NGINX_OS` environment variable.
51+
/// The list of recognized values will be exported as `DEP_NGINX_OS_CHECK`.
52+
const NGX_CONF_OS: &[&str] = &[
53+
"darwin", "freebsd", "gnu_hurd", "hpux", "linux", "solaris", "tru64", "win32",
54+
];
55+
1356
/// Function invoked when `cargo build` is executed.
1457
/// This function will download NGINX and all supporting dependencies, verify their integrity,
1558
/// extract them, execute autoconf `configure` for NGINX, compile NGINX and finally install
@@ -37,11 +80,14 @@ fn main() -> Result<(), Box<dyn StdError>> {
3780
/// Generates Rust bindings for NGINX
3881
fn generate_binding(nginx_build_dir: PathBuf) {
3982
let autoconf_makefile_path = nginx_build_dir.join("Makefile");
40-
let clang_args: Vec<String> = parse_includes_from_makefile(&autoconf_makefile_path)
41-
.into_iter()
83+
let includes = parse_includes_from_makefile(&autoconf_makefile_path);
84+
let clang_args: Vec<String> = includes
85+
.iter()
4286
.map(|path| format!("-I{}", path.to_string_lossy()))
4387
.collect();
4488

89+
print_cargo_metadata(&includes).expect("cargo dependency metadata");
90+
4591
let bindings = bindgen::Builder::default()
4692
// Bindings will not compile on Linux without block listing this item
4793
// It is worth investigating why this is
@@ -132,3 +178,95 @@ fn parse_includes_from_makefile(nginx_autoconf_makefile_path: &PathBuf) -> Vec<P
132178
})
133179
.collect()
134180
}
181+
182+
/// Collect info about the nginx configuration and expose it to the dependents via
183+
/// `DEP_NGINX_...` variables.
184+
pub fn print_cargo_metadata<T: AsRef<Path>>(includes: &[T]) -> Result<(), Box<dyn StdError>> {
185+
// Unquote and merge C string constants
186+
let unquote_re = regex::Regex::new(r#""(.*?[^\\])"\s*"#).unwrap();
187+
let unquote = |data: &str| -> String {
188+
unquote_re
189+
.captures_iter(data)
190+
.map(|c| c.get(1).unwrap().as_str())
191+
.collect::<Vec<_>>()
192+
.concat()
193+
};
194+
195+
let mut ngx_features: Vec<String> = vec![];
196+
let mut ngx_os = String::new();
197+
198+
let expanded = expand_definitions(includes)?;
199+
for line in String::from_utf8(expanded)?.lines() {
200+
let Some((name, value)) = line.trim().strip_prefix("RUST_CONF_").and_then(|x| x.split_once('=')) else {
201+
continue;
202+
};
203+
204+
let name = name.trim().to_ascii_lowercase();
205+
let value = value.trim();
206+
207+
if name == "nginx_build" {
208+
println!("cargo::metadata=build={}", unquote(value));
209+
} else if name == "nginx_version" {
210+
println!("cargo::metadata=version={}", unquote(value));
211+
} else if name == "nginx_version_number" {
212+
println!("cargo::metadata=version_number={value}");
213+
} else if NGX_CONF_OS.contains(&name.as_str()) {
214+
ngx_os = name;
215+
} else if NGX_CONF_FEATURES.contains(&name.as_str()) && value != "0" {
216+
ngx_features.push(name);
217+
}
218+
}
219+
220+
println!(
221+
"cargo::metadata=include={}",
222+
// The str conversion is necessary because cargo directives must be valid UTF-8
223+
env::join_paths(includes.iter().map(|x| x.as_ref()))?
224+
.to_str()
225+
.expect("Unicode include paths")
226+
);
227+
228+
// A quoted list of all recognized features to be passed to rustc-check-cfg.
229+
println!("cargo::metadata=features_check=\"{}\"", NGX_CONF_FEATURES.join("\",\""));
230+
// A list of features enabled in the nginx build we're using
231+
println!("cargo::metadata=features={}", ngx_features.join(","));
232+
233+
// A quoted list of all recognized operating systems to be passed to rustc-check-cfg.
234+
println!("cargo::metadata=os_check=\"{}\"", NGX_CONF_OS.join("\",\""));
235+
// Current detected operating system
236+
println!("cargo::metadata=os={ngx_os}");
237+
238+
Ok(())
239+
}
240+
241+
fn expand_definitions<T: AsRef<Path>>(includes: &[T]) -> Result<Vec<u8>, Box<dyn StdError>> {
242+
let path = PathBuf::from(env::var("OUT_DIR")?).join("expand.c");
243+
let mut writer = std::io::BufWriter::new(File::create(&path)?);
244+
245+
write!(
246+
writer,
247+
"
248+
#include <ngx_config.h>
249+
#include <nginx.h>
250+
251+
RUST_CONF_NGINX_BUILD=NGINX_VER_BUILD
252+
RUST_CONF_NGINX_VERSION=NGINX_VER
253+
RUST_CONF_NGINX_VERSION_NUMBER=nginx_version
254+
"
255+
)?;
256+
257+
for flag in NGX_CONF_FEATURES.iter().chain(NGX_CONF_OS.iter()) {
258+
let flag = flag.to_ascii_uppercase();
259+
write!(
260+
writer,
261+
"
262+
#if defined(NGX_{flag})
263+
RUST_CONF_{flag}=NGX_{flag}
264+
#endif"
265+
)?;
266+
}
267+
268+
writer.flush()?;
269+
drop(writer);
270+
271+
Ok(cc::Build::new().includes(includes).file(path).try_expand()?)
272+
}

0 commit comments

Comments
 (0)