From df6d5abfd852db9b94fde4ba121e48bc64d04ecb Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 16 Apr 2024 01:09:37 -0700 Subject: [PATCH 1/6] feat(sys): use prebuilt NGINX tree if $NGX_OBJS is specified This should help integrating the modules into the NGINX build process with `--add-dynamic-module`. --- nginx-sys/Cargo.toml | 1 + nginx-sys/build/main.rs | 130 ++++++++++++++++++++++ nginx-sys/{build.rs => build/vendored.rs} | 110 +----------------- nginx-sys/{ => build}/wrapper.h | 0 4 files changed, 135 insertions(+), 106 deletions(-) create mode 100644 nginx-sys/build/main.rs rename nginx-sys/{build.rs => build/vendored.rs} (88%) rename nginx-sys/{ => build}/wrapper.h (100%) diff --git a/nginx-sys/Cargo.toml b/nginx-sys/Cargo.toml index bd2d6ada..cfe52c4b 100644 --- a/nginx-sys/Cargo.toml +++ b/nginx-sys/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/nginxinc/ngx-rust" homepage = "https://github.com/nginxinc/ngx-rust" license = "Apache-2.0" keywords = ["nginx", "ffi", "sys"] +build = "build/main.rs" [lib] crate-type = ["staticlib", "rlib"] diff --git a/nginx-sys/build/main.rs b/nginx-sys/build/main.rs new file mode 100644 index 00000000..7662cb9c --- /dev/null +++ b/nginx-sys/build/main.rs @@ -0,0 +1,130 @@ +extern crate bindgen; + +use std::env; +use std::error::Error as StdError; +use std::fs::read_to_string; +use std::path::PathBuf; + +mod vendored; + +const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 2] = ["OUT_DIR", "NGX_OBJS"]; + +/// Function invoked when `cargo build` is executed. +/// This function will download NGINX and all supporting dependencies, verify their integrity, +/// extract them, execute autoconf `configure` for NGINX, compile NGINX and finally install +/// NGINX in a subdirectory with the project. +fn main() -> Result<(), Box> { + let nginx_build_dir = if let Ok(v) = env::var("NGX_OBJS") { + PathBuf::from(v).canonicalize()? + } else { + vendored::build()? + }; + // Hint cargo to rebuild if any of the these environment variables values change + // because they will trigger a recompilation of NGINX with different parameters + for var in ENV_VARS_TRIGGERING_RECOMPILE { + println!("cargo:rerun-if-env-changed={var}"); + } + println!("cargo:rerun-if-changed=build/main.rs"); + println!("cargo:rerun-if-changed=build/wrapper.h"); + // Read autoconf generated makefile for NGINX and generate Rust bindings based on its includes + generate_binding(nginx_build_dir); + Ok(()) +} + +/// Generates Rust bindings for NGINX +fn generate_binding(nginx_build_dir: PathBuf) { + let autoconf_makefile_path = nginx_build_dir.join("Makefile"); + let clang_args: Vec = parse_includes_from_makefile(&autoconf_makefile_path) + .into_iter() + .map(|path| format!("-I{}", path.to_string_lossy())) + .collect(); + + let bindings = bindgen::Builder::default() + // Bindings will not compile on Linux without block listing this item + // It is worth investigating why this is + .blocklist_item("IPPORT_RESERVED") + // The input header we would like to generate bindings for. + .header("build/wrapper.h") + .clang_args(clang_args) + .layout_tests(false) + .generate() + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_dir_env = env::var("OUT_DIR").expect("The required environment variable OUT_DIR was not set"); + let out_path = PathBuf::from(out_dir_env); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +/// Reads through the makefile generated by autoconf and finds all of the includes +/// used to compile nginx. This is used to generate the correct bindings for the +/// nginx source code. +fn parse_includes_from_makefile(nginx_autoconf_makefile_path: &PathBuf) -> Vec { + fn extract_include_part(line: &str) -> &str { + line.strip_suffix('\\').map_or(line, |s| s.trim()) + } + /// Extracts the include path from a line of the autoconf generated makefile. + fn extract_after_i_flag(line: &str) -> Option<&str> { + let mut parts = line.split("-I "); + match parts.next() { + Some(_) => parts.next().map(extract_include_part), + None => None, + } + } + + let mut includes = vec![]; + let makefile_contents = match read_to_string(nginx_autoconf_makefile_path) { + Ok(path) => path, + Err(e) => { + panic!( + "Unable to read makefile from path [{}]. Error: {}", + nginx_autoconf_makefile_path.to_string_lossy(), + e + ); + } + }; + + let mut includes_lines = false; + for line in makefile_contents.lines() { + if !includes_lines { + if let Some(stripped) = line.strip_prefix("ALL_INCS") { + includes_lines = true; + if let Some(part) = extract_after_i_flag(stripped) { + includes.push(part); + } + continue; + } + } + + if includes_lines { + if let Some(part) = extract_after_i_flag(line) { + includes.push(part); + } else { + break; + } + } + } + + let makefile_dir = nginx_autoconf_makefile_path + .parent() + .expect("makefile path has no parent") + .parent() + .expect("objs dir has no parent") + .to_path_buf() + .canonicalize() + .expect("Unable to canonicalize makefile path"); + + includes + .into_iter() + .map(PathBuf::from) + .map(|path| { + if path.is_absolute() { + path + } else { + makefile_dir.join(path) + } + }) + .collect() +} diff --git a/nginx-sys/build.rs b/nginx-sys/build/vendored.rs similarity index 88% rename from nginx-sys/build.rs rename to nginx-sys/build/vendored.rs index 284e0cfb..6f4d35ac 100644 --- a/nginx-sys/build.rs +++ b/nginx-sys/build/vendored.rs @@ -1,4 +1,3 @@ -extern crate bindgen; extern crate duct; use std::collections::HashMap; @@ -103,7 +102,6 @@ const NGX_LINUX_ADDITIONAL_OPTS: [&str; 3] = [ ]; const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 12] = [ "DEBUG", - "OUT_DIR", "ZLIB_VERSION", "PCRE2_VERSION", "OPENSSL_VERSION", @@ -112,15 +110,15 @@ const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 12] = [ "CARGO_MANIFEST_DIR", "CARGO_TARGET_TMPDIR", "CACHE_DIR", + "NGX_DEBUG", "NGX_INSTALL_ROOT_DIR", "NGX_INSTALL_DIR", ]; -/// Function invoked when `cargo build` is executed. /// This function will download NGINX and all supporting dependencies, verify their integrity, /// extract them, execute autoconf `configure` for NGINX, compile NGINX and finally install /// NGINX in a subdirectory with the project. -fn main() -> Result<(), Box> { +pub fn build() -> Result> { println!("Building NGINX"); // Create .cache directory let cache_dir = make_cache_dir()?; @@ -140,38 +138,9 @@ fn main() -> Result<(), Box> { for var in ENV_VARS_TRIGGERING_RECOMPILE { println!("cargo:rerun-if-env-changed={var}"); } - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=wrapper.h"); - // Read autoconf generated makefile for NGINX and generate Rust bindings based on its includes - generate_binding(nginx_src_dir); - Ok(()) -} + println!("cargo:rerun-if-changed=build/vendored.rs"); -/// Generates Rust bindings for NGINX -fn generate_binding(nginx_source_dir: PathBuf) { - let autoconf_makefile_path = nginx_source_dir.join("objs").join("Makefile"); - let clang_args: Vec = parse_includes_from_makefile(&autoconf_makefile_path) - .into_iter() - .map(|path| format!("-I{}", path.to_string_lossy())) - .collect(); - - let bindings = bindgen::Builder::default() - // Bindings will not compile on Linux without block listing this item - // It is worth investigating why this is - .blocklist_item("IPPORT_RESERVED") - // The input header we would like to generate bindings for. - .header("wrapper.h") - .clang_args(clang_args) - .layout_tests(false) - .generate() - .expect("Unable to generate bindings"); - - // Write the bindings to the $OUT_DIR/bindings.rs file. - let out_dir_env = env::var("OUT_DIR").expect("The required environment variable OUT_DIR was not set"); - let out_path = PathBuf::from(out_dir_env); - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("Couldn't write bindings!"); + Ok(nginx_src_dir.join("objs")) } /* @@ -698,74 +667,3 @@ fn make(nginx_src_dir: &Path, arg: &str) -> std::io::Result { .stderr_to_stdout() .run() } - -/// Reads through the makefile generated by autoconf and finds all of the includes -/// used to compile nginx. This is used to generate the correct bindings for the -/// nginx source code. -fn parse_includes_from_makefile(nginx_autoconf_makefile_path: &PathBuf) -> Vec { - fn extract_include_part(line: &str) -> &str { - line.strip_suffix('\\').map_or(line, |s| s.trim()) - } - /// Extracts the include path from a line of the autoconf generated makefile. - fn extract_after_i_flag(line: &str) -> Option<&str> { - let mut parts = line.split("-I "); - match parts.next() { - Some(_) => parts.next().map(extract_include_part), - None => None, - } - } - - let mut includes = vec![]; - let makefile_contents = match read_to_string(nginx_autoconf_makefile_path) { - Ok(path) => path, - Err(e) => { - panic!( - "Unable to read makefile from path [{}]. Error: {}", - nginx_autoconf_makefile_path.to_string_lossy(), - e - ); - } - }; - - let mut includes_lines = false; - for line in makefile_contents.lines() { - if !includes_lines { - if let Some(stripped) = line.strip_prefix("ALL_INCS") { - includes_lines = true; - if let Some(part) = extract_after_i_flag(stripped) { - includes.push(part); - } - continue; - } - } - - if includes_lines { - if let Some(part) = extract_after_i_flag(line) { - includes.push(part); - } else { - break; - } - } - } - - let makefile_dir = nginx_autoconf_makefile_path - .parent() - .expect("makefile path has no parent") - .parent() - .expect("objs dir has no parent") - .to_path_buf() - .canonicalize() - .expect("Unable to canonicalize makefile path"); - - includes - .into_iter() - .map(PathBuf::from) - .map(|path| { - if path.is_absolute() { - path - } else { - makefile_dir.join(path) - } - }) - .collect() -} diff --git a/nginx-sys/wrapper.h b/nginx-sys/build/wrapper.h similarity index 100% rename from nginx-sys/wrapper.h rename to nginx-sys/build/wrapper.h From cfb3cf7692b764f952b19cfe45c6da54647c3d14 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 5 Mar 2024 23:12:12 -0800 Subject: [PATCH 2/6] feat(sys): make deps required for building NGINX optional These dependencies are not necessary when using an existing NGINX source tree. --- Cargo.toml | 6 ++++++ nginx-sys/Cargo.toml | 13 ++++++++----- nginx-sys/build/main.rs | 11 +++++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f013af72..eb8a53d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,12 @@ keywords = ["nginx", "module", "sys"] [dependencies] nginx-sys = { path = "nginx-sys", version = "0.5.0"} +[features] +# Build our own copy of the NGINX by default. +# This could be disabled with `--no-default-features` to minimize the dependency tree +# when building against an existing copy of the NGINX with the NGX_OBJS variable. +default = ["nginx-sys/vendored"] + [badges] maintenance = { status = "experimental" } diff --git a/nginx-sys/Cargo.toml b/nginx-sys/Cargo.toml index cfe52c4b..a1d97dbc 100644 --- a/nginx-sys/Cargo.toml +++ b/nginx-sys/Cargo.toml @@ -17,8 +17,11 @@ crate-type = ["staticlib", "rlib"] [build-dependencies] bindgen = "0.69.4" -which = "6.0.0" -duct = "0.13.7" -ureq = { version = "2.9.6", features = ["tls"] } -flate2 = "1.0.28" -tar = "0.4.40" +duct = { version = "0.13.7", optional = true } +flate2 = { version = "1.0.28", optional = true } +tar = { version = "0.4.40", optional = true } +ureq = { version = "2.9.6", features = ["tls"], optional = true } +which = { version = "6.0.0", optional = true } + +[features] +vendored = ["dep:which", "dep:duct", "dep:ureq", "dep:flate2", "dep:tar"] diff --git a/nginx-sys/build/main.rs b/nginx-sys/build/main.rs index 7662cb9c..1bdc033c 100644 --- a/nginx-sys/build/main.rs +++ b/nginx-sys/build/main.rs @@ -5,6 +5,7 @@ use std::error::Error as StdError; use std::fs::read_to_string; use std::path::PathBuf; +#[cfg(feature = "vendored")] mod vendored; const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 2] = ["OUT_DIR", "NGX_OBJS"]; @@ -14,10 +15,12 @@ const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 2] = ["OUT_DIR", "NGX_OBJS"]; /// extract them, execute autoconf `configure` for NGINX, compile NGINX and finally install /// NGINX in a subdirectory with the project. fn main() -> Result<(), Box> { - let nginx_build_dir = if let Ok(v) = env::var("NGX_OBJS") { - PathBuf::from(v).canonicalize()? - } else { - vendored::build()? + let nginx_build_dir = match std::env::var("NGX_OBJS") { + Ok(v) => PathBuf::from(v).canonicalize()?, + #[cfg(feature = "vendored")] + Err(_) => vendored::build()?, + #[cfg(not(feature = "vendored"))] + Err(_) => panic!("\"nginx-sys/vendored\" feature is disabled and NGX_OBJS is not specified"), }; // Hint cargo to rebuild if any of the these environment variables values change // because they will trigger a recompilation of NGINX with different parameters From d653666297a7f9d937840f5c80290492797ef95d Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 17 Apr 2024 10:29:51 -0700 Subject: [PATCH 3/6] feat(sys): generate string constants as CStr Breaking, as it changes the type of all the string constants in the bindings. --- nginx-sys/build/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/nginx-sys/build/main.rs b/nginx-sys/build/main.rs index 1bdc033c..97f4ef97 100644 --- a/nginx-sys/build/main.rs +++ b/nginx-sys/build/main.rs @@ -46,6 +46,7 @@ fn generate_binding(nginx_build_dir: PathBuf) { // Bindings will not compile on Linux without block listing this item // It is worth investigating why this is .blocklist_item("IPPORT_RESERVED") + .generate_cstr(true) // The input header we would like to generate bindings for. .header("build/wrapper.h") .clang_args(clang_args) From 23b2d8dd0ba580e73bc2aaff46da9411a5ecf9fa Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 16 Apr 2024 01:09:22 -0700 Subject: [PATCH 4/6] fix: test execution with external NGINX source tree --- nginx-sys/build/wrapper.h | 9 ++++++ tests/log_test.rs | 58 +++++++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/nginx-sys/build/wrapper.h b/nginx-sys/build/wrapper.h index c53f3c97..1f1d17e7 100644 --- a/nginx-sys/build/wrapper.h +++ b/nginx-sys/build/wrapper.h @@ -9,3 +9,12 @@ const size_t NGX_RS_HTTP_SRV_CONF_OFFSET = NGX_HTTP_SRV_CONF_OFFSET; const size_t NGX_RS_HTTP_LOC_CONF_OFFSET = NGX_HTTP_LOC_CONF_OFFSET; const char *NGX_RS_MODULE_SIGNATURE = NGX_MODULE_SIGNATURE; + +// `--prefix=` results in not emitting the declaration +#ifndef NGX_PREFIX +#define NGX_PREFIX "" +#endif + +#ifndef NGX_CONF_PREFIX +#define NGX_CONF_PREFIX NGX_PREFIX +#endif diff --git a/tests/log_test.rs b/tests/log_test.rs index 1ad05bc0..874aa3e8 100644 --- a/tests/log_test.rs +++ b/tests/log_test.rs @@ -1,38 +1,57 @@ -use std::env; use std::fs; use std::io::Result; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; use std::process::Command; use std::process::Output; -const NGX_DEFAULT_VERSION: &str = "1.24.0"; -const NGINX_BIN: &str = "sbin/nginx"; -const NGINX_CONFIG: &str = "conf/nginx.conf"; +use ngx::ffi::{NGX_CONF_PATH, NGX_PREFIX, NGX_SBIN_PATH}; + +/// Convert a CStr to a PathBuf +pub fn cstr_to_path(val: &std::ffi::CStr) -> Option { + if val.is_empty() { + return None; + } + + #[cfg(unix)] + let str = std::ffi::OsStr::from_bytes(val.to_bytes()); + #[cfg(not(unix))] + let str = std::str::from_utf8(val.to_bytes()).ok()?; + + Some(PathBuf::from(str)) +} /// harness to test nginx pub struct Nginx { - pub install_path: String, + pub install_path: PathBuf, + pub config_path: PathBuf, } impl Default for Nginx { /// create nginx with default fn default() -> Nginx { - let path = env::current_dir().unwrap(); - let version = env::var("NGX_VERSION").unwrap_or(NGX_DEFAULT_VERSION.into()); - let platform = target_triple::TARGET; - let ngx_path = format!(".cache/nginx/{}/{}", version, platform); - let install_path = format!("{}/{}", path.display(), ngx_path); - Nginx { install_path } + let install_path = cstr_to_path(NGX_PREFIX).expect("installation prefix"); + Nginx::new(install_path) } } impl Nginx { - pub fn new(path: String) -> Nginx { - Nginx { install_path: path } + pub fn new>(path: P) -> Nginx { + let install_path = path.as_ref(); + let config_path = cstr_to_path(NGX_CONF_PATH).expect("configuration path"); + let config_path = install_path.join(config_path); + + Nginx { + install_path: install_path.into(), + config_path, + } } /// get bin path to nginx instance - pub fn bin_path(&mut self) -> String { - format!("{}/{}", self.install_path, NGINX_BIN) + pub fn bin_path(&mut self) -> PathBuf { + let bin_path = cstr_to_path(NGX_SBIN_PATH).expect("binary path"); + self.install_path.join(bin_path) } /// start nginx process with arguments @@ -70,10 +89,9 @@ impl Nginx { } // replace config with another config - pub fn replace_config(&mut self, from: &str) -> Result { - let config_path = format!("{}/{}", self.install_path, NGINX_CONFIG); - println!("copying config from: {} to: {}", from, config_path); // replace with logging - fs::copy(from, config_path) + pub fn replace_config>(&mut self, from: P) -> Result { + println!("copying config from: {:?} to: {:?}", from.as_ref(), self.config_path); // replace with logging + fs::copy(from, &self.config_path) } } @@ -99,7 +117,7 @@ mod tests { ); nginx - .replace_config(&test_config_path.to_string_lossy()) + .replace_config(&test_config_path) .expect(format!("Unable to load config file: {}", test_config_path.to_string_lossy()).as_str()); let output = nginx.restart().expect("Unable to restart NGINX"); assert!(output.status.success()); From 3aebcdb7854474719783b7d44485eecfbc87c74b Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 17 Apr 2024 16:21:50 -0700 Subject: [PATCH 5/6] feat: example of NGINX buildsystem integration --- examples/Cargo.toml | 7 +++ examples/async.rs | 8 +++- examples/awssig.rs | 8 +++- examples/config | 94 +++++++++++++++++++++++++++++++++++++++++ examples/config.make | 35 +++++++++++++++ examples/curl.rs | 8 +++- examples/httporigdst.rs | 8 +++- examples/upstream.rs | 8 +++- 8 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 examples/config create mode 100644 examples/config.make diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f38ee587..0de756cc 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -42,4 +42,11 @@ crate-type = ["cdylib"] tokio = { version = "1.33.0", features = ["full"] } [features] +default = ["export-modules"] +# Generate `ngx_modules` table with module exports +# The exports table is required for building loadable modules with --crate-type cdylib +# outside of the NGINX buildsystem. However, cargo currently does not detect +# this configuration automatically. +# See https://github.com/rust-lang/rust/issues/20267 +export-modules = [] linux = [] diff --git a/examples/async.rs b/examples/async.rs index b82f3e8e..d98a5a03 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -6,7 +6,7 @@ use ngx::ffi::{ }; use ngx::http::MergeConfigError; use ngx::{core, core::Status, http, http::HTTPModule}; -use ngx::{http_request_handler, ngx_log_debug_http, ngx_modules, ngx_null_command, ngx_string}; +use ngx::{http_request_handler, ngx_log_debug_http, ngx_null_command, ngx_string}; use std::os::raw::{c_char, c_void}; use std::ptr::{addr_of, addr_of_mut}; use std::sync::atomic::AtomicBool; @@ -78,9 +78,13 @@ static ngx_http_async_module_ctx: ngx_http_module_t = ngx_http_module_t { merge_loc_conf: Some(Module::merge_loc_conf), }; -ngx_modules!(ngx_http_async_module); +// Generate the `ngx_modules` table with exported modules. +// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. +#[cfg(feature = "export-modules")] +ngx::ngx_modules!(ngx_http_async_module); #[no_mangle] +#[used] pub static mut ngx_http_async_module: ngx_module_t = ngx_module_t { ctx_index: ngx_uint_t::max_value(), index: ngx_uint_t::max_value(), diff --git a/examples/awssig.rs b/examples/awssig.rs index aa37eaed..e85e7283 100644 --- a/examples/awssig.rs +++ b/examples/awssig.rs @@ -6,7 +6,7 @@ use ngx::ffi::{ NGX_RS_HTTP_LOC_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, }; use ngx::{core, core::Status, http::*}; -use ngx::{http_request_handler, ngx_log_debug_http, ngx_modules, ngx_null_command, ngx_string}; +use ngx::{http_request_handler, ngx_log_debug_http, ngx_null_command, ngx_string}; use std::os::raw::{c_char, c_void}; use std::ptr::addr_of; @@ -97,9 +97,13 @@ static ngx_http_awssigv4_module_ctx: ngx_http_module_t = ngx_http_module_t { merge_loc_conf: Some(Module::merge_loc_conf), }; -ngx_modules!(ngx_http_awssigv4_module); +// Generate the `ngx_modules` table with exported modules. +// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. +#[cfg(feature = "export-modules")] +ngx::ngx_modules!(ngx_http_awssigv4_module); #[no_mangle] +#[used] pub static mut ngx_http_awssigv4_module: ngx_module_t = ngx_module_t { ctx_index: ngx_uint_t::max_value(), index: ngx_uint_t::max_value(), diff --git a/examples/config b/examples/config new file mode 100644 index 00000000..7de72bec --- /dev/null +++ b/examples/config @@ -0,0 +1,94 @@ +ngx_addon_name=ngx_rust_examples +ngx_cargo_profile=ngx-module + +if [ $HTTP = YES ]; then + ngx_module_type=HTTP + + if :; then + ngx_module_name=ngx_http_async_module + ngx_module_lib=async + + ngx_module_lib=$NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_module_lib.a + ngx_module_deps=$ngx_module_lib + ngx_module_libs="$ngx_module_lib -lm" + + # Module deps are usually added to the object file targets, but we don't have any + LINK_DEPS="$LINK_DEPS $ngx_module_lib" + + . auto/module + fi + + if :; then + ngx_module_name=ngx_http_awssigv4_module + ngx_module_lib=awssig + + ngx_module_lib=$NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_module_lib.a + ngx_module_deps=$ngx_module_lib + ngx_module_libs=$ngx_module_lib + + # Module deps are usually added to the object file targets, but we don't have any + LINK_DEPS="$LINK_DEPS $ngx_module_lib" + + . auto/module + fi + + if :; then + ngx_module_name=ngx_http_curl_module + ngx_module_lib=curl + + ngx_module_lib=$NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_module_lib.a + ngx_module_deps=$ngx_module_lib + ngx_module_libs=$ngx_module_lib + + # Module deps are usually added to the object file targets, but we don't have any + LINK_DEPS="$LINK_DEPS $ngx_module_lib" + + . auto/module + fi + + case "$NGX_PLATFORM" in + Linux:*) + ngx_module_name=ngx_http_orig_dst_module + ngx_module_lib=httporigdst + + ngx_module_lib=$NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_module_lib.a + ngx_module_deps=$ngx_module_lib + ngx_module_libs=$ngx_module_lib + + # Module deps are usually added to the object file targets, but we don't have any + LINK_DEPS="$LINK_DEPS $ngx_module_lib" + + . auto/module + ;; + esac + + if :; then + ngx_module_name=ngx_http_upstream_custom_module + ngx_module_lib=upstream + + ngx_module_lib=$NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_module_lib.a + ngx_module_deps=$ngx_module_lib + ngx_module_libs=$ngx_module_lib + + # Module deps are usually added to the object file targets, but we don't have any + LINK_DEPS="$LINK_DEPS $ngx_module_lib" + + . auto/module + fi +fi + +# Write a cargo config with the $ngx_cargo_profile definition (optional) + +if [ "$NGX_DEBUG" = YES ]; then + NGX_CARGO_PROFILE_BASE=dev +else + NGX_CARGO_PROFILE_BASE=release +fi + +mkdir -p "$NGX_OBJS/.cargo" +cat > "$NGX_OBJS/.cargo/config.toml" << END + +[profile.$ngx_cargo_profile] +inherits = "$NGX_CARGO_PROFILE_BASE" + +END diff --git a/examples/config.make b/examples/config.make new file mode 100644 index 00000000..fff246b7 --- /dev/null +++ b/examples/config.make @@ -0,0 +1,35 @@ +ngx_addon_name=ngx_rust_examples +ngx_cargo_profile=ngx-module +ngx_cargo_manifest=$(realpath $ngx_addon_dir/Cargo.toml) +ngx_cargo_features= +ngx_rust_examples="async awssig curl upstream" + +case "$NGX_PLATFORM" in + Linux:*) + ngx_cargo_features="$ngx_cargo_features linux" + ngx_rust_examples="$ngx_rust_examples httporigdst" + ;; +esac + +for ngx_rust_example in $ngx_rust_examples +do + +cat << END >> $NGX_MAKEFILE + +# Always call cargo instead of tracking the source modifications +.PHONY: $NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_rust_example.a + +$NGX_OBJS/$ngx_addon_name/$ngx_cargo_profile/examples/lib$ngx_rust_example.a: + cd $NGX_OBJS && \\ + NGX_OBJS="\$\$PWD" cargo rustc \\ + --crate-type staticlib \\ + --example "$ngx_rust_example" \\ + --no-default-features \\ + --features "$ngx_cargo_features" \\ + --profile $ngx_cargo_profile \\ + --target-dir $ngx_addon_name \\ + --manifest-path $ngx_cargo_manifest + +END + +done diff --git a/examples/curl.rs b/examples/curl.rs index 0dcda829..2675bb46 100644 --- a/examples/curl.rs +++ b/examples/curl.rs @@ -6,7 +6,7 @@ use ngx::ffi::{ }; use ngx::http::MergeConfigError; use ngx::{core, core::Status, http, http::HTTPModule}; -use ngx::{http_request_handler, ngx_log_debug_http, ngx_modules, ngx_null_command, ngx_string}; +use ngx::{http_request_handler, ngx_log_debug_http, ngx_null_command, ngx_string}; use std::os::raw::{c_char, c_void}; use std::ptr::addr_of; @@ -61,9 +61,13 @@ static ngx_http_curl_module_ctx: ngx_http_module_t = ngx_http_module_t { merge_loc_conf: Some(Module::merge_loc_conf), }; -ngx_modules!(ngx_http_curl_module); +// Generate the `ngx_modules` table with exported modules. +// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. +#[cfg(feature = "export-modules")] +ngx::ngx_modules!(ngx_http_curl_module); #[no_mangle] +#[used] pub static mut ngx_http_curl_module: ngx_module_t = ngx_module_t { ctx_index: ngx_uint_t::max_value(), index: ngx_uint_t::max_value(), diff --git a/examples/httporigdst.rs b/examples/httporigdst.rs index 1e62e273..c31def22 100644 --- a/examples/httporigdst.rs +++ b/examples/httporigdst.rs @@ -5,7 +5,7 @@ use ngx::ffi::{ NGX_RS_MODULE_SIGNATURE, }; use ngx::{core, core::Status, http, http::HTTPModule}; -use ngx::{http_variable_get, ngx_http_null_variable, ngx_log_debug_http, ngx_modules, ngx_null_string, ngx_string}; +use ngx::{http_variable_get, ngx_http_null_variable, ngx_log_debug_http, ngx_null_string, ngx_string}; use std::os::raw::{c_char, c_int, c_void}; const IPV4_STRLEN: usize = INET_ADDRSTRLEN as usize; @@ -86,9 +86,13 @@ static ngx_http_orig_dst_module_ctx: ngx_http_module_t = ngx_http_module_t { merge_loc_conf: Some(Module::merge_loc_conf), }; -ngx_modules!(ngx_http_orig_dst_module); +// Generate the `ngx_modules` table with exported modules. +// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. +#[cfg(feature = "export-modules")] +ngx::ngx_modules!(ngx_http_orig_dst_module); #[no_mangle] +#[used] pub static mut ngx_http_orig_dst_module: ngx_module_t = ngx_module_t { ctx_index: ngx_uint_t::max_value(), index: ngx_uint_t::max_value(), diff --git a/examples/upstream.rs b/examples/upstream.rs index 5a48792a..ba507c06 100644 --- a/examples/upstream.rs +++ b/examples/upstream.rs @@ -22,7 +22,7 @@ use ngx::{ }, http_upstream_init_peer_pt, log::DebugMask, - ngx_log_debug_http, ngx_log_debug_mask, ngx_modules, ngx_null_command, ngx_string, + ngx_log_debug_http, ngx_log_debug_mask, ngx_null_command, ngx_string, }; use std::{ mem, @@ -105,9 +105,13 @@ static mut ngx_http_upstream_custom_commands: [ngx_command_t; 2] = [ ngx_null_command!(), ]; -ngx_modules!(ngx_http_upstream_custom_module); +// Generate the `ngx_modules` table with exported modules. +// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. +#[cfg(feature = "export-modules")] +ngx::ngx_modules!(ngx_http_upstream_custom_module); #[no_mangle] +#[used] pub static mut ngx_http_upstream_custom_module: ngx_module_t = ngx_module_t { ctx_index: ngx_uint_t::max_value(), index: ngx_uint_t::max_value(), From 91a042bf646cbb3ba3d3c71b4f42f18170238612 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 23 Apr 2024 11:22:49 -0700 Subject: [PATCH 6/6] doc: document integration with the NGINX buildsystem --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index d74aebdf..00066f2a 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,21 @@ Example modules are available in [examples](examples) folder. You can use `cargo For example (all examples plus linux specific): `cargo build --package=examples --examples --features=linux` +### Build with external NGINX source tree + +If you require a customized NGINX configuration, you can build a module against an existing pre-configured source tree. +To do that, you need to set the `NGX_OBJS` variable to an _absolute_ path of the NGINX build directory (`--builddir`, defaults to the `objs` in the source directory). +Only the `./configure` step of the NGINX build is mandatory because bindings don't depend on any of the artifacts generated by `make`. + + +``` +NGX_OBJS=$PWD/../nginx/objs cargo build --package=examples --examples + +``` + +Furthermore, this approach can be leveraged to build a module as a part of the NGINX build process by adding the `--add-module`/`--add-dynamic-module` options to the configure script. +See the following example integration scripts: [`examples/config`](examples/config) and [`examples/config.make`](examples/config.make). + ### Docker We provide a multistage [Dockerfile](Dockerfile):