├── .github └── workflows │ └── autotools-rs-compact.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── xmp-sys │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ └── lib.rs └── src └── lib.rs /.github/workflows/autotools-rs-compact.yml: -------------------------------------------------------------------------------- 1 | name: autotools-rs-compact 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | clippy-rustfmt: 13 | 14 | strategy: 15 | matrix: 16 | platform: [ubuntu-latest, macos-latest, windows-latest] 17 | 18 | runs-on: ${{ matrix.platform }} 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Install stable 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | components: clippy, rustfmt 30 | 31 | - name: Run rustfmt 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: -- --check --verbose 36 | 37 | - name: Run cargo clippy 38 | uses: actions-rs/clippy-check@v1 39 | with: 40 | token: ${{ secrets.GITHUB_TOKEN }} 41 | args: --all-targets --tests --benches -- -D warnings 42 | 43 | build-test: 44 | 45 | strategy: 46 | matrix: 47 | platform: [ubuntu-latest, macos-latest, windows-latest] 48 | 49 | runs-on: ${{ matrix.platform }} 50 | 51 | steps: 52 | - uses: actions/checkout@v3 53 | 54 | - name: Install Rust stable 55 | uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | toolchain: stable 59 | override: true 60 | 61 | - name: Build 62 | run: cargo build --verbose --tests --benches 63 | 64 | - name: Run tests 65 | run: cargo test --verbose 66 | 67 | - name: Generate docs 68 | run: cargo doc --no-deps 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "example/xmp-sys/libxmp"] 2 | path = examples/xmp-sys/libxmp 3 | url = https://github.com/cmatsuoka/libxmp/ 4 | [submodule "examples/libhello-sys"] 5 | path = examples/libhello-sys 6 | url = https://github.com/ahmed-masud/libhello-sys.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | before_install: 9 | - git submodule update --init --recursive 10 | 11 | script: 12 | # build and test the crate 13 | - cargo test --verbose 14 | # build the example 15 | - cargo build --verbose --manifest-path=examples/xmp-sys/Cargo.toml 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autotools" 3 | version = "0.2.7" 4 | authors = ["Luca Barbato "] 5 | license = "MIT" 6 | keywords = ["build-dependencies"] 7 | repository = "https://github.com/lu-zero/autotools-rs" 8 | homepage = "https://github.com/lu-zero/autotools-rs" 9 | description = """ 10 | A build dependency to build native libraries that use configure&make-style build systems 11 | """ 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | cc = "1.0.3" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Luca Barbato 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autotools/configure&make support for build.rs 2 | 3 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 4 | [![dependency status](https://deps.rs/repo/github/lu-zero/autotools-rs/status.svg)](https://deps.rs/repo/github/lu-zero/autotools-rs) 5 | [![crates.io](https://img.shields.io/crates/v/autotools.svg?style=flat)](https://crates.io/crates/autotools) 6 | [![docs.rs](https://docs.rs/autotools/badge.svg)](https://docs.rs/autotools) 7 | [![Actions Status](https://github.com/lu-zero/autotools-rs/workflows/autotools-rs-compact/badge.svg)](https://github.com/lu-zero/autotools-rs/actions) 8 | 9 | 10 | A build dependency to compile a native library that uses [autotools][1] or 11 | a compatible `configure` script + `make`. 12 | 13 | It is based on [cmake-rs](https://github.com/alexcrichton/cmake-rs) and 14 | the API tries to be as similar as possible to it. 15 | 16 | ## Autotools concern 17 | The generated `configure` script that is often bundled in release tarballs tends to be fairly big, convoluted and at least once has been a vector for 18 | delivering malicious code ([CVE-2024-3094][cve-xz]. 19 | 20 | It is advised to review `configure.ac` and always regenerate `configure` using [`reconf`][reconf]. 21 | 22 | [cve-xz]: https://nvd.nist.gov/vuln/detail/CVE-2024-3094 23 | [reconf]: https://docs.rs/autotools/latest/autotools/struct.Config.html#method.reconf 24 | 25 | ## Cross compiling 26 | 27 | ### Emscripten 28 | For Emscripten targets like "wasm32-unknown-emscripten", `configure` and 29 | `make` invocations are passed as arguments to `emconfigure` and `emmake` 30 | respectively as described in the [Emscripten docs](https://emscripten.org/docs/compiling/Building-Projects.html#integrating-with-a-build-system). 31 | 32 | ### Custom LLVM on macOS 33 | Make sure to set the env to `CC=clang-{version}` and that the compiler is in the `PATH`. If you are using [install-llvm-action](https://github.com/KyleMayes/install-llvm-action), 34 | make sure to set [`env: true`](https://github.com/KyleMayes/install-llvm-action#env). 35 | 36 | ### Other compilers 37 | Keep in mind that we rely on `cc-rs` heuristics to select the C and C++ compilers. Some may be missing on your system, please make sure to set 38 | the [enviroment variables](https://github.com/rust-lang/cc-rs#external-configuration-via-environment-variables) to select the correct compiler if 39 | the heuristics fail (e.g. `musl-gcc` might not exist while `x86_64-linux-musl-gcc` does). 40 | 41 | 42 | 43 | ``` toml 44 | # Cargo.toml 45 | [build-dependencies] 46 | autotools = "0.2" 47 | ``` 48 | 49 | ``` rust 50 | // build.rs 51 | use autotools; 52 | 53 | // Build the project in the path `foo` and installs it in `$OUT_DIR` 54 | let dst = autotools::build("foo"); 55 | 56 | // Simply link the library without using pkg-config 57 | println!("cargo:rustc-link-search=native={}", dst.display()); 58 | println!("cargo:rustc-link-lib=static=foo"); 59 | ``` 60 | 61 | ``` rust 62 | // build.rs 63 | use autotools::Config; 64 | 65 | let dst = Config::new("foo") 66 | .reconf("-ivf") 67 | .enable("feature", None) 68 | .with("dep", None) 69 | .disable("otherfeature", None) 70 | .without("otherdep", None) 71 | .cflag("-Wall") 72 | .build(); 73 | ``` 74 | 75 | [1]: https://www.gnu.org/software/autoconf/autoconf.html 76 | -------------------------------------------------------------------------------- /examples/xmp-sys/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/rust 3 | # Edit at https://www.gitignore.io/?templates=rust 4 | 5 | ### Rust ### 6 | # Generated by Cargo 7 | # will have compiled files and executables 8 | /target/ 9 | 10 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 11 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 12 | Cargo.lock 13 | 14 | # These are backup files generated by rustfmt 15 | **/*.rs.bk 16 | 17 | # End of https://www.gitignore.io/api/rust 18 | -------------------------------------------------------------------------------- /examples/xmp-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmp-sys" 3 | version = "0.1.0" 4 | authors = ["Martin Larralde "] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "lib.rs" 9 | 10 | [build-dependencies.autotools] 11 | path = "../.." 12 | 13 | [build-dependencies.bindgen] 14 | version = "*" 15 | -------------------------------------------------------------------------------- /examples/xmp-sys/build.rs: -------------------------------------------------------------------------------- 1 | extern crate autotools; 2 | extern crate bindgen; 3 | 4 | use std::path::PathBuf; 5 | use std::env::var; 6 | 7 | fn main() { 8 | // Build the project insource, only building lib/libxmp.a 9 | let dst = autotools::Config::new("libxmp") 10 | .reconf("-v") 11 | .make_target("lib/libxmp.a") 12 | .insource(true) 13 | .build(); 14 | 15 | // Simply link the library without using pkg-config 16 | println!("cargo:rustc-link-search=native={}", dst.join("lib").display()); 17 | println!("cargo:rustc-link-lib=static=xmp"); 18 | println!("cargo:rustc-link-lib=c"); 19 | 20 | // generate bindings using bindgen 21 | let bindings = bindgen::Builder::default() 22 | .header("libxmp/include/xmp.h") 23 | .generate() 24 | .expect("unable to generate bindings"); 25 | 26 | // setup the path to write bindings into 27 | let out_path = PathBuf::from(var("OUT_DIR").unwrap()); 28 | bindings 29 | .write_to_file(out_path.join("bindings.rs")) 30 | .expect("Couldn't write bindings!"); 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /examples/xmp-sys/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] 2 | 3 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 4 | 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A build dependency for running the correct autotools commands to build a native library 2 | //! 3 | //! This crate provides the facilities to setup the build system and build native libraries 4 | //! that leverage `autotools` or `configure & make` workalike scripts. 5 | //! 6 | //! ## Autotools `configure` concern 7 | //! The generated `configure` script that is often bundled in release tarballs tends to be fairly big, convoluted and at least once has been a vector for 8 | //! delivering malicious code ([CVE-2024-3094][cve-xz]). 9 | //! 10 | //! It is advised to review `configure.ac` and always regenerate `configure` using [`reconf`][reconf]. 11 | //! 12 | //! [cve-xz]: https://nvd.nist.gov/vuln/detail/CVE-2024-3094 13 | //! [reconf]: https://docs.rs/autotools/latest/autotools/struct.Config.html#method.reconf 14 | //! 15 | //! ## Installation 16 | //! 17 | //! Add to your `Cargo.toml` a build dependency: 18 | //! 19 | //! ```toml 20 | //! [build-dependencies] 21 | //! autotools = "0.2" 22 | //! ``` 23 | //! 24 | //! ## Usage 25 | //! 26 | //! ```no_run 27 | //! use autotools; 28 | //! 29 | //! // Build the project in the path `foo` and installs it in `$OUT_DIR` 30 | //! let dst = autotools::build("foo"); 31 | //! 32 | //! // Simply link the library without using pkg-config 33 | //! println!("cargo:rustc-link-search=native={}", dst.display()); 34 | //! println!("cargo:rustc-link-lib=static=foo"); 35 | //! ``` 36 | //! 37 | //! ```no_run 38 | //! use autotools::Config; 39 | //! 40 | //! let dst = Config::new("foo") 41 | //! .reconf("-ivf") 42 | //! .enable("feature", None) 43 | //! .with("dep", None) 44 | //! .disable("otherfeature", None) 45 | //! .without("otherdep", None) 46 | //! .cflag("-Wall") 47 | //! .build(); 48 | //! ``` 49 | 50 | extern crate cc; 51 | 52 | use std::collections::HashSet; 53 | use std::env; 54 | use std::ffi::{OsStr, OsString}; 55 | use std::fs; 56 | use std::io::ErrorKind; 57 | use std::path::{Path, PathBuf}; 58 | use std::process::Command; 59 | use std::str; 60 | 61 | enum Kind { 62 | Enable, 63 | Disable, 64 | With, 65 | Without, 66 | Arbitrary, 67 | } 68 | 69 | /// Builder style configuration for a pending autotools build. 70 | /// 71 | /// # Note 72 | /// 73 | /// Note that `host` and `target` have different meanings for Rust 74 | /// than for Gnu autotools. For Rust, the "host" machine is the one where the 75 | /// compiler is running, and the "target" machine is the one where the 76 | /// compiled artifact (library or binary) will be executed. 77 | /// For Gnu autotools, the machine where the compiler is running is called 78 | /// the "build" machine; the one where the compiled artifact will be 79 | /// executed is called the "host" machine; and if the compiled artifact 80 | /// happens to be a cross-compiler, it will generate code for a "target" 81 | /// machine; otherwise "target" will coincide with "host". 82 | /// 83 | /// Hence Rust's `host` corresponds to Gnu autotools' "build" and Rust's 84 | /// `target` corresponds to their "host" (though the relevant names will sometimes 85 | /// differ slightly). 86 | /// 87 | /// The `host` and `target` methods on this package's `autotools::Config` structure (as well as 88 | /// the `$HOST` and `$TARGET` variables set by cargo) are understood with their 89 | /// Rust meaning. 90 | /// 91 | /// When cross-compiling, we try to calculate automatically what Gnu autotools will expect for its 92 | /// "host" value, and supply that to the `configure` script using a `--host="..."` argument. If the 93 | /// auto-calculation is incorrect, you can override it with the `config_option` method, like this: 94 | /// 95 | /// ```no_run 96 | /// use autotools; 97 | /// 98 | /// // Builds the project in the directory located in `libfoo`, installing it 99 | /// // into $OUT_DIR 100 | /// let mut cfg = autotools::Config::new("libfoo_source_directory"); 101 | /// cfg.config_option("host", Some("i686-pc-windows-gnu")); 102 | /// let dst = cfg.build(); 103 | /// 104 | /// println!("cargo:rustc-link-search=native={}", dst.display()); 105 | /// println!("cargo:rustc-link-lib=static=foo"); 106 | /// ``` 107 | pub struct Config { 108 | enable_shared: bool, 109 | enable_static: bool, 110 | path: PathBuf, 111 | cflags: OsString, 112 | cxxflags: OsString, 113 | ldflags: OsString, 114 | options: Vec<(Kind, OsString, Option)>, 115 | target: Option, 116 | make_args: Option>, 117 | make_targets: Option>, 118 | host: Option, 119 | out_dir: Option, 120 | env: Vec<(OsString, OsString)>, 121 | reconfig: Option, 122 | build_insource: bool, 123 | forbidden_args: HashSet, 124 | fast_build: bool, 125 | } 126 | 127 | /// Builds the native library rooted at `path` with the default configure options. 128 | /// This will return the directory in which the library was installed. 129 | /// 130 | /// # Examples 131 | /// 132 | /// ```no_run 133 | /// use autotools; 134 | /// 135 | /// // Builds the project in the directory located in `libfoo`, installing it 136 | /// // into $OUT_DIR 137 | /// let dst = autotools::build("libfoo"); 138 | /// 139 | /// println!("cargo:rustc-link-search=native={}", dst.display()); 140 | /// println!("cargo:rustc-link-lib=static=foo"); 141 | /// ``` 142 | /// 143 | pub fn build>(path: P) -> PathBuf { 144 | Config::new(path.as_ref()).build() 145 | } 146 | 147 | impl Config { 148 | /// Creates a new blank set of configuration to build the project specified 149 | /// at the path `path`. 150 | pub fn new>(path: P) -> Config { 151 | match Config::try_new(path) { 152 | Ok(config) => config, 153 | Err(msg) => fail(&msg), 154 | } 155 | } 156 | 157 | /// Tries to create a new blank set of configuration to build the project specified 158 | /// at the path `path`. 159 | pub fn try_new>(path: P) -> Result { 160 | // test that `sh` is present and does what we want--see `new_command` below 161 | // sidestep the whole "execute permission" thing by only checking shebang functionality on Windows 162 | let arg: String = if cfg!(windows) { 163 | let out_dir = env::var_os("OUT_DIR").expect("missing OUT_DIR"); 164 | let path = PathBuf::from(out_dir).join("test.sh"); 165 | fs::write(&path, "#!/bin/sh\ntrue\n").expect("can't write to OUT_DIR"); 166 | // escape path (double the escape for double the fun!) 167 | // (seriously it will break otherwise) 168 | path.to_str() 169 | .expect("invalid UTF-8 in path") 170 | .escape_default() 171 | .flat_map(char::escape_default) 172 | .collect() 173 | } else { 174 | "true".into() 175 | }; 176 | 177 | if let Ok(output) = Command::new("sh") 178 | .arg("-c") 179 | .arg(format!("echo test; {}", arg)) 180 | .output() 181 | { 182 | if !output.status.success() { 183 | // Print `sh` output for debugging 184 | println!("{}", str::from_utf8(&output.stdout).unwrap_or_default()); 185 | eprintln!("{}", str::from_utf8(&output.stderr).unwrap_or_default()); 186 | 187 | if cfg!(windows) && output.stdout == b"test\n" { 188 | return Err("`sh` does not parse shebangs".to_owned()); 189 | } else { 190 | return Err("`sh` is not standard or is otherwise broken".to_owned()); 191 | } 192 | } 193 | } else { 194 | return Err("`sh` is required to run `configure`".to_owned()); 195 | } 196 | 197 | Ok(Config { 198 | enable_shared: false, 199 | enable_static: true, 200 | path: env::current_dir().unwrap().join(path), 201 | cflags: OsString::new(), 202 | cxxflags: OsString::new(), 203 | ldflags: OsString::new(), 204 | options: Vec::new(), 205 | make_args: None, 206 | make_targets: None, 207 | out_dir: None, 208 | target: None, 209 | host: None, 210 | env: Vec::new(), 211 | reconfig: None, 212 | build_insource: false, 213 | forbidden_args: HashSet::new(), 214 | fast_build: false, 215 | }) 216 | } 217 | 218 | /// Enables building as a shared library (`--enable-shared`). 219 | pub fn enable_shared(&mut self) -> &mut Config { 220 | self.enable_shared = true; 221 | self 222 | } 223 | 224 | /// Disables building as a shared library (`--disable-shared`). 225 | pub fn disable_shared(&mut self) -> &mut Config { 226 | self.enable_shared = false; 227 | self 228 | } 229 | 230 | /// Enables building as a static library (`--enable-static`). 231 | pub fn enable_static(&mut self) -> &mut Config { 232 | self.enable_static = true; 233 | self 234 | } 235 | 236 | /// Disables building as a static library (`--disable-static`). 237 | pub fn disable_static(&mut self) -> &mut Config { 238 | self.enable_static = false; 239 | self 240 | } 241 | 242 | /// Additional arguments to pass through to `make`. 243 | pub fn make_args(&mut self, flags: Vec) -> &mut Config { 244 | self.make_args = Some(flags); 245 | self 246 | } 247 | 248 | fn set_opt>(&mut self, kind: Kind, opt: P, optarg: Option

) -> &mut Config { 249 | let optarg = optarg.as_ref().map(|v| v.as_ref().to_owned()); 250 | self.options.push((kind, opt.as_ref().to_owned(), optarg)); 251 | self 252 | } 253 | 254 | /// Passes `--<=optarg>` to configure. 255 | pub fn config_option>(&mut self, opt: P, optarg: Option

) -> &mut Config { 256 | self.set_opt(Kind::Arbitrary, opt, optarg) 257 | } 258 | 259 | /// Passes `--enable-<=optarg>` to configure. 260 | pub fn enable>(&mut self, opt: P, optarg: Option

) -> &mut Config { 261 | self.set_opt(Kind::Enable, opt, optarg) 262 | } 263 | 264 | /// Passes `--disable-<=optarg>` to configure. 265 | pub fn disable>(&mut self, opt: P, optarg: Option

) -> &mut Config { 266 | self.set_opt(Kind::Disable, opt, optarg) 267 | } 268 | 269 | /// Passes `--with-<=optarg>` to configure. 270 | pub fn with>(&mut self, opt: P, optarg: Option

) -> &mut Config { 271 | self.set_opt(Kind::With, opt, optarg) 272 | } 273 | 274 | /// Passes `--without-<=optarg>` to configure. 275 | pub fn without>(&mut self, opt: P, optarg: Option

) -> &mut Config { 276 | self.set_opt(Kind::Without, opt, optarg) 277 | } 278 | 279 | /// Adds a custom flag to pass down to the C compiler, supplementing those 280 | /// that this library already passes. 281 | /// 282 | /// Default flags for the chosen compiler have lowest priority, then any 283 | /// flags from the environment variable `$CFLAGS`, then any flags specified 284 | /// with this method. 285 | pub fn cflag>(&mut self, flag: P) -> &mut Config { 286 | self.cflags.push(" "); 287 | self.cflags.push(flag.as_ref()); 288 | self 289 | } 290 | 291 | /// Adds a custom flag to pass down to the C++ compiler, supplementing those 292 | /// that this library already passes. 293 | /// 294 | /// Default flags for the chosen compiler have lowest priority, then any 295 | /// flags from the environment variable `$CXXFLAGS`, then any flags specified 296 | /// with this method. 297 | pub fn cxxflag>(&mut self, flag: P) -> &mut Config { 298 | self.cxxflags.push(" "); 299 | self.cxxflags.push(flag.as_ref()); 300 | self 301 | } 302 | 303 | /// Adds a custom flag to pass down to the linker, supplementing those 304 | /// that this library already passes. 305 | /// 306 | /// Flags from the environment variable `$LDFLAGS` have lowest priority, 307 | /// then any flags specified with this method. 308 | pub fn ldflag>(&mut self, flag: P) -> &mut Config { 309 | self.ldflags.push(" "); 310 | self.ldflags.push(flag.as_ref()); 311 | self 312 | } 313 | 314 | /// Sets the target triple for this compilation. 315 | /// 316 | /// This is automatically scraped from `$TARGET` which is set for Cargo 317 | /// build scripts so it's not necessary to call this from a build script. 318 | /// 319 | /// See [Note](#main) on the differences between Rust's and autotools' 320 | /// interpretation of "target" (this method assumes the former). 321 | pub fn target(&mut self, target: &str) -> &mut Config { 322 | self.target = Some(target.to_string()); 323 | self 324 | } 325 | 326 | /// Sets the host triple for this compilation. 327 | /// 328 | /// This is automatically scraped from `$HOST` which is set for Cargo 329 | /// build scripts so it's not necessary to call this from a build script. 330 | /// 331 | /// See [Note](#main) on the differences between Rust's and autotools' 332 | /// interpretation of "host" (this method assumes the former). 333 | pub fn host(&mut self, host: &str) -> &mut Config { 334 | self.host = Some(host.to_string()); 335 | self 336 | } 337 | 338 | /// Sets the output directory for this compilation. 339 | /// 340 | /// This is automatically scraped from `$OUT_DIR` which is set for Cargo 341 | /// build scripts so it's not necessary to call this from a build script. 342 | pub fn out_dir>(&mut self, out: P) -> &mut Config { 343 | self.out_dir = Some(out.as_ref().to_path_buf()); 344 | self 345 | } 346 | 347 | /// Configure an environment variable for the `configure && make` processes 348 | /// spawned by this crate in the `build` step. 349 | /// 350 | /// If you want to set `$CFLAGS`, `$CXXFLAGS`, or `$LDFLAGS`, consider using the 351 | /// [cflag](#method.cflag), 352 | /// [cxxflag](#method.cxxflag), or 353 | /// [ldflag](#method.ldflag) 354 | /// methods instead, which will append to any external 355 | /// values. Setting those environment variables here will overwrite the 356 | /// external values, and will also discard any flags determined by the chosen 357 | /// compiler. 358 | /// 359 | /// `autotools::Config` will automatically pass `$CC` and `$CXX` values to 360 | /// the `configure` script based on the chosen compiler. Setting those 361 | /// variables here will override, and interferes with other parts of this 362 | /// library, so is not recommended. 363 | pub fn env(&mut self, key: K, value: V) -> &mut Config 364 | where 365 | K: AsRef, 366 | V: AsRef, 367 | { 368 | self.env 369 | .push((key.as_ref().to_owned(), value.as_ref().to_owned())); 370 | self 371 | } 372 | 373 | /// Options to pass through to `autoreconf` prior to configuring the build. 374 | pub fn reconf>(&mut self, flags: P) -> &mut Config { 375 | self.reconfig = Some(flags.as_ref().to_os_string()); 376 | self 377 | } 378 | 379 | /// Build the given make target. 380 | /// 381 | /// If this function is not called, the build will default to `make install`. 382 | pub fn make_target(&mut self, make_target: &str) -> &mut Config { 383 | self.make_targets 384 | .get_or_insert_with(Vec::new) 385 | .push(make_target.to_owned()); 386 | self 387 | } 388 | 389 | /// Build the library in-source. 390 | /// 391 | /// This is generally not recommended, but can be required for libraries that 392 | /// make extensive use of nested Makefiles, which cannot be included in 393 | /// out-of-source builds. 394 | pub fn insource(&mut self, build_insource: bool) -> &mut Config { 395 | self.build_insource = build_insource; 396 | self 397 | } 398 | 399 | /// Forbid an argument from being added to the configure command. 400 | /// 401 | /// This can be used to account for non-standard configure scripts. 402 | pub fn forbid(&mut self, arg: T) -> &mut Config { 403 | self.forbidden_args.insert(arg.to_string()); 404 | self 405 | } 406 | 407 | /// Enable fast building (which skips over configure if there is no) 408 | /// change in the configuration parameters. 409 | pub fn fast_build(&mut self, fast: bool) -> &mut Config { 410 | self.fast_build = fast; 411 | self 412 | } 413 | 414 | fn try_get_paths(&self) -> Result<(PathBuf, PathBuf), String> { 415 | if self.build_insource { 416 | let dst = self.path.clone(); 417 | let build = dst.clone(); 418 | Ok((dst, build)) 419 | } else { 420 | let dst = match self.out_dir.clone() { 421 | Some(dst) => dst, 422 | None => PathBuf::from(try_getenv_unwrap("OUT_DIR")?), 423 | }; 424 | let build = dst.join("build"); 425 | self.maybe_clear(&build); 426 | let _ = fs::create_dir(&build); 427 | Ok((dst, build)) 428 | } 429 | } 430 | 431 | /// Run this configuration 432 | /// 433 | /// This will run only the build system generator. 434 | pub fn configure(&mut self) -> PathBuf { 435 | match self.try_configure() { 436 | Ok(path) => path, 437 | Err(msg) => fail(&msg), 438 | } 439 | } 440 | 441 | /// Try to run this configuration 442 | /// 443 | /// This will run only the build system generator, returning an error message on failure. 444 | pub fn try_configure(&mut self) -> Result { 445 | let target = match self.target.clone() { 446 | Some(target) => target, 447 | None => try_getenv_unwrap("TARGET")?, 448 | }; 449 | let host = match self.host.clone() { 450 | Some(host) => host, 451 | None => try_getenv_unwrap("HOST")?, 452 | }; 453 | let mut c_cfg = cc::Build::new(); 454 | c_cfg 455 | .cargo_metadata(false) 456 | .target(&target) 457 | .warnings(false) 458 | .host(&host); 459 | let mut cxx_cfg = cc::Build::new(); 460 | cxx_cfg 461 | .cargo_metadata(false) 462 | .cpp(true) 463 | .target(&target) 464 | .warnings(false) 465 | .host(&host); 466 | let c_compiler = c_cfg.get_compiler(); 467 | let cxx_compiler = cxx_cfg.get_compiler(); 468 | 469 | let (dst, build) = self.try_get_paths()?; 470 | 471 | // TODO: env overrides? 472 | // TODO: PKG_CONFIG_PATH 473 | if let Some(ref opts) = self.reconfig { 474 | let executable = "autoreconf".to_owned(); 475 | let mut cmd = new_command(executable); 476 | cmd.current_dir(&self.path); 477 | 478 | try_run(cmd.arg(opts), "autoreconf")?; 479 | } 480 | 481 | let mut cmd; 482 | let mut program = "configure"; 483 | let mut args: Vec = Vec::new(); 484 | let executable = PathBuf::from(&self.path).join(program); 485 | if target.contains("emscripten") { 486 | program = "emconfigure"; 487 | cmd = new_command(program); 488 | args.push(executable.to_string_lossy().to_string()); 489 | } else { 490 | cmd = new_command(executable); 491 | } 492 | 493 | // TODO: discuss whether we should replace this 494 | // with DESTDIR or something 495 | args.push(format!("--prefix={}", dst.display())); 496 | 497 | if cfg!(windows) { 498 | // `configure` is hardcoded to fail on characters it deems "unsafe" found in a path-- 499 | // including '\', i.e. the Windows path separator. It will happily pull a Windows-style path 500 | // for `srcdir` on its own, and then immediately complain about it. Hopefully we're building 501 | // in a Cygwin/MSYS environment that can give us a path that will make it happy. 502 | let cygpath = Command::new("cygpath") 503 | .args(["--unix", "--codepage=UTF8"]) 504 | .args([&dst, &self.path]) 505 | .output(); 506 | if let Ok(output) = cygpath { 507 | if output.status.success() { 508 | let output = String::from_utf8(output.stdout).unwrap(); 509 | let mut lines = output.lines(); 510 | let prefix = lines.next().unwrap(); 511 | let srcdir = lines.next().unwrap(); 512 | args.push(format!("--prefix={}", prefix)); 513 | args.push(format!("--srcdir={}", srcdir)); 514 | } 515 | } 516 | } 517 | 518 | if self.enable_shared { 519 | args.push("--enable-shared".to_string()); 520 | } else { 521 | args.push("--disable-shared".to_string()); 522 | } 523 | 524 | if self.enable_static { 525 | args.push("--enable-static".to_string()); 526 | } else { 527 | args.push("--disable-static".to_string()); 528 | } 529 | 530 | let mut cflags = c_compiler.cflags_env(); 531 | match env::var_os("CFLAGS") { 532 | None => (), 533 | Some(flags) => { 534 | cflags.push(" "); 535 | cflags.push(&flags); 536 | } 537 | } 538 | if !self.cflags.is_empty() { 539 | cflags.push(" "); 540 | cflags.push(&self.cflags); 541 | } 542 | cmd.env("CFLAGS", cflags); 543 | 544 | let mut cxxflags = cxx_compiler.cflags_env(); 545 | match env::var_os("CXXFLAGS") { 546 | None => (), 547 | Some(flags) => { 548 | cxxflags.push(" "); 549 | cxxflags.push(&flags); 550 | } 551 | } 552 | if !self.cxxflags.is_empty() { 553 | cxxflags.push(" "); 554 | cxxflags.push(&self.cxxflags); 555 | } 556 | cmd.env("CXXFLAGS", cxxflags); 557 | 558 | if !self.ldflags.is_empty() { 559 | match env::var_os("LDFLAGS") { 560 | None => cmd.env("LDFLAGS", &self.ldflags), 561 | Some(flags) => { 562 | let mut os = flags; 563 | os.push(" "); 564 | os.push(&self.ldflags); 565 | cmd.env("LDFLAGS", &os) 566 | } 567 | }; 568 | } 569 | 570 | let mut config_host = false; 571 | 572 | for (kind, k, v) in &self.options { 573 | let mut os = OsString::from("--"); 574 | match *kind { 575 | Kind::Enable => os.push("enable-"), 576 | Kind::Disable => os.push("disable-"), 577 | Kind::With => os.push("with-"), 578 | Kind::Without => os.push("without-"), 579 | Kind::Arbitrary => { 580 | if k == "host" { 581 | config_host = true; 582 | } 583 | } 584 | }; 585 | os.push(k); 586 | if let Some(v) = v { 587 | os.push("="); 588 | os.push(v); 589 | } 590 | args.push(os.to_string_lossy().to_string()); 591 | } 592 | 593 | let cc_path = c_compiler.path().to_str().unwrap(); 594 | let cxx_path = cxx_compiler.path().to_str().unwrap(); 595 | 596 | if !config_host && cc_path != "musl-gcc" { 597 | let host = cc_path 598 | .strip_suffix("-cc") 599 | .or_else(|| cc_path.strip_suffix("-gcc")); 600 | if let Some(host) = host { 601 | args.push(format!("--host={}", host)); 602 | } 603 | } 604 | 605 | cmd.env("CC", cc_path); 606 | cmd.env("CXX", cxx_path); 607 | 608 | for (k, v) in c_compiler.env().iter().chain(&self.env) { 609 | cmd.env(k, v); 610 | } 611 | 612 | for (k, v) in cxx_compiler.env().iter().chain(&self.env) { 613 | cmd.env(k, v); 614 | } 615 | 616 | cmd.args(args.iter().filter(|x| { 617 | !self.forbidden_args.contains(match x.find('=') { 618 | Some(idx) => x.split_at(idx).0, 619 | None => x.as_str(), 620 | }) 621 | })); 622 | 623 | // attempt to see if we were previously configured with the same flags 624 | // if so, then we can skip running configure 625 | let run_config = if self.fast_build { 626 | let config_status_file = build.join("config.status"); 627 | let config_params_file = build.join("configure.prev"); 628 | let makefile = build.join("Makefile"); 629 | if config_status_file.exists() && config_params_file.exists() && makefile.exists() { 630 | let mut config_params = String::new(); 631 | let mut f = fs::File::open(&config_params_file).unwrap(); 632 | std::io::Read::read_to_string(&mut f, &mut config_params).unwrap(); 633 | config_params != format!("{:?}", cmd) 634 | } else { 635 | true 636 | } 637 | } else { 638 | true 639 | }; 640 | 641 | if run_config { 642 | let config_params_file = build.join("configure.prev"); 643 | let mut f = fs::File::create(config_params_file).unwrap(); 644 | std::io::Write::write_all(&mut f, format!("{:?}", cmd).as_bytes()).unwrap(); 645 | try_run(cmd.current_dir(&build), program)?; 646 | } 647 | 648 | Ok(dst) 649 | } 650 | 651 | /// Run this configuration, compiling the library with all the configured 652 | /// options. 653 | /// 654 | /// This will run both the build system generator command as well as the 655 | /// command to build the library. 656 | pub fn build(&mut self) -> PathBuf { 657 | match self.try_build() { 658 | Ok(path) => path, 659 | Err(msg) => fail(&msg), 660 | } 661 | } 662 | 663 | /// Try to run this configuration, compiling the library with all the configured 664 | /// options. 665 | /// 666 | /// This will run both the build system generator command as well as the 667 | /// command to build the library. If it fails it will return an error message 668 | pub fn try_build(&mut self) -> Result { 669 | self.try_configure()?; 670 | 671 | let (dst, build) = self.try_get_paths()?; 672 | 673 | let target = match self.target.clone() { 674 | Some(target) => target, 675 | None => try_getenv_unwrap("TARGET")?, 676 | }; 677 | 678 | // interestingly if configure needs to be rerun because of any 679 | // dependencies the make will use config.status to run it anyhow. 680 | // Build up the first make command to build the build system. 681 | let mut program = "make"; 682 | let mut cmd; 683 | let executable = env::var("MAKE").unwrap_or_else(|_| program.to_owned()); 684 | if target.contains("emscripten") { 685 | program = "emmake"; 686 | cmd = new_command("emmake"); 687 | cmd.arg(executable); 688 | } else { 689 | cmd = new_command(executable); 690 | } 691 | cmd.current_dir(&build); 692 | 693 | let mut makeflags = None; 694 | let mut make_args = Vec::new(); 695 | 696 | if let Some(args) = &self.make_args { 697 | make_args.extend_from_slice(args); 698 | } 699 | 700 | if let Ok(num_jobs_s) = env::var("NUM_JOBS") { 701 | // This looks like `make`, let's hope it understands `-jN`. 702 | make_args.push(format!("-j{}", num_jobs_s)); 703 | match env::var_os("CARGO_MAKEFLAGS") { 704 | // Only do this on non-windows and non-bsd 705 | // On Windows, we could be invoking make instead of 706 | // mingw32-make which doesn't work with our jobserver 707 | // bsdmake also does not work with our job server 708 | Some(ref cargo_make_flags) 709 | if !(cfg!(windows) 710 | || cfg!(target_os = "openbsd") 711 | || cfg!(target_os = "netbsd") 712 | || cfg!(target_os = "freebsd") 713 | || cfg!(target_os = "bitrig") 714 | || cfg!(target_os = "dragonflybsd")) => 715 | { 716 | makeflags = Some(cargo_make_flags.clone()) 717 | } 718 | _ => (), 719 | } 720 | } 721 | 722 | // And build! 723 | let make_targets = self.make_targets.get_or_insert(vec!["install".to_string()]); 724 | if let Some(flags) = makeflags { 725 | cmd.env("MAKEFLAGS", flags); 726 | } 727 | 728 | try_run( 729 | cmd.args(make_targets).args(&make_args).current_dir(&build), 730 | program, 731 | )?; 732 | 733 | println!("cargo:root={}", dst.display()); 734 | Ok(dst) 735 | } 736 | 737 | fn maybe_clear(&self, _dir: &Path) { 738 | // TODO: make clean? 739 | } 740 | } 741 | 742 | fn try_run(cmd: &mut Command, program: &str) -> Result<(), String> { 743 | println!("running: {:?}", cmd); 744 | let status = match cmd.status() { 745 | Ok(status) => status, 746 | Err(ref e) if e.kind() == ErrorKind::NotFound => { 747 | return Err(format!( 748 | "failed to execute command: {}\nis `{}` not installed?", 749 | e, program 750 | )); 751 | } 752 | Err(e) => { 753 | return Err(format!("failed to execute command: {}", e)); 754 | } 755 | }; 756 | if !status.success() { 757 | return Err(format!( 758 | "command did not execute successfully, got: {}", 759 | status 760 | )); 761 | } 762 | Ok(()) 763 | } 764 | 765 | // Windows users cannot execute `./configure` (shell script) or `autoreconf` (Perl script) directly 766 | // like everyone else in the world can. However, the Cygwin compatibility layer handles the task of 767 | // reading the shebang of any file an application tries to "execute" (in lieu of a kernel doing the same), 768 | // and transparently invokes the referenced executable just like a Unix user would expect. 769 | // 770 | // Long story short, this function assumes two things: 771 | // 1. `sh` exists on PATH (kind of hard to run `./configure` without that, huh) 772 | // 2. If on Windows, `sh` lives in magical Cygwin land and can parse shebangs for us (thus preserving 773 | // functionality between Windows and everyone else) 774 | // Prepare a process::Command wherein the program is invoked within `sh`. 775 | // The presence of `sh` is verified in Config::new above. 776 | fn new_command>(program: S) -> Command { 777 | let mut cmd = Command::new("sh"); 778 | cmd.args(["-c", "exec \"$0\" \"$@\""]).arg(program); 779 | cmd 780 | } 781 | 782 | fn try_getenv_unwrap(v: &str) -> Result { 783 | match env::var(v) { 784 | Ok(s) => Ok(s), 785 | Err(..) => Err(format!("environment variable `{}` not defined", v)), 786 | } 787 | } 788 | 789 | fn fail(s: &str) -> ! { 790 | panic!("\n{}\n\nbuild script failed, must exit now", s) 791 | } 792 | --------------------------------------------------------------------------------