├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .release-plz.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src └── lib.rs └── test-crate ├── .gitignore ├── Cargo.toml └── src └── lib.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | env: 5 | RUSTDOCFLAGS: -D warnings 6 | RUSTFLAGS: -D warnings 7 | 8 | jobs: 9 | clippy: 10 | name: clippy 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install Rust 15 | run: | 16 | rustup update beta --no-self-update 17 | rustup default beta 18 | rustup component add clippy 19 | - uses: Swatinem/rust-cache@v2 20 | - run: cargo clippy --all-features --all-targets -- -D warnings 21 | 22 | test: 23 | name: Test 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | os: [ubuntu-latest, macos-latest, windows-latest] 28 | rust: ["1.70", stable, beta, nightly] 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Install Rust 32 | run: | 33 | rustup update ${{ matrix.rust }} --no-self-update 34 | rustup default ${{ matrix.rust }} 35 | - uses: Swatinem/rust-cache@v2 36 | - run: cargo test 37 | - name: Integration test 38 | run: cargo test --manifest-path test-crate/Cargo.toml 39 | 40 | cross_compile_test: 41 | name: Test Cross Compile - ${{ matrix.platform.target }} 42 | needs: [ test ] 43 | runs-on: ubuntu-latest 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | platform: 48 | # Testable 49 | - target: aarch64-unknown-linux-gnu 50 | test: true 51 | - target: arm-unknown-linux-gnueabihf 52 | test: true 53 | - target: powerpc-unknown-linux-gnu 54 | test: true 55 | - target: s390x-unknown-linux-gnu 56 | test: true 57 | - target: x86_64-unknown-linux-musl 58 | test: true 59 | - target: aarch64-unknown-linux-musl 60 | test: true 61 | # Build only 62 | - target: x86_64-pc-solaris 63 | test: false 64 | - target: x86_64-pc-windows-gnu 65 | test: false 66 | # FIXME: build fails, see 67 | # - target: x86_64-unknown-freebsd 68 | # test: false 69 | - target: x86_64-unknown-netbsd 70 | test: false 71 | - target: x86_64-unknown-illumos 72 | test: false 73 | steps: 74 | - uses: actions/checkout@master 75 | - name: Install Rust 76 | run: | 77 | rustup update stable --no-self-update 78 | rustup default stable 79 | rustup target add ${{ matrix.platform.target }} 80 | - uses: taiki-e/install-action@v2 81 | with: 82 | tool: cross 83 | - uses: Swatinem/rust-cache@v2 84 | - name: cross test 85 | run: cross test -vv --target ${{ matrix.platform.target }} 86 | working-directory: test-crate 87 | if: ${{ matrix.platform.test }} 88 | - name: cross build 89 | run: cross build -vv --target ${{ matrix.platform.target }} 90 | working-directory: test-crate 91 | if: ${{ !matrix.platform.test }} 92 | 93 | ios_cross_compile_test: 94 | name: Test Cross Compile - ${{ matrix.platform.target }} 95 | needs: [ test ] 96 | runs-on: macos-latest 97 | strategy: 98 | fail-fast: false 99 | matrix: 100 | platform: 101 | - target: aarch64-apple-ios 102 | steps: 103 | - uses: actions/checkout@v4 104 | - name: Install Rust 105 | run: | 106 | rustup update stable --no-self-update 107 | rustup default stable 108 | rustup target add ${{ matrix.platform.target }} 109 | - uses: Swatinem/rust-cache@v2 110 | - name: build 111 | run: cargo build -vv --target ${{ matrix.platform.target }} 112 | working-directory: test-crate 113 | env: 114 | # If this isn't specified the default is iOS 7, for which zlib-ng will not compile due to the lack of thread-local storage. 115 | IPHONEOS_DEPLOYMENT_TARGET: 16 116 | 117 | rustfmt: 118 | name: Rustfmt 119 | runs-on: ubuntu-latest 120 | steps: 121 | - uses: actions/checkout@master 122 | - name: Install Rust 123 | run: | 124 | rustup update stable --no-self-update 125 | rustup default stable 126 | rustup component add rustfmt 127 | - run: cargo fmt --all -- --check 128 | 129 | doc: 130 | name: docs 131 | runs-on: ubuntu-latest 132 | steps: 133 | - uses: actions/checkout@v4 134 | - run: | 135 | rustup update nightly --no-self-update 136 | rustup default nightly 137 | - uses: Swatinem/rust-cache@v2 138 | - run: cargo doc 139 | 140 | success: 141 | needs: 142 | - clippy 143 | - test 144 | - cross_compile_test 145 | - ios_cross_compile_test 146 | - rustfmt 147 | - doc 148 | runs-on: ubuntu-latest 149 | # GitHub branch protection is exceedingly silly and treats "jobs skipped because a dependency 150 | # failed" as success. So we have to do some contortions to ensure the job fails if any of its 151 | # dependencies fails. 152 | if: always() # make sure this is never "skipped" 153 | steps: 154 | # Manually check the status of all dependencies. `if: failure()` does not work. 155 | - name: check if any dependency failed 156 | run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' 157 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust (rustup) 22 | run: rustup update stable --no-self-update && rustup default stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | git_tag_name = "v{{ version }}" 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.1.54](https://github.com/rust-lang/cmake-rs/compare/v0.1.53...v0.1.54) - 2025-02-10 6 | 7 | ### Other 8 | 9 | - Remove workaround for broken `cc-rs` versions ([#235](https://github.com/rust-lang/cmake-rs/pull/235)) 10 | - Be more precise in the description of `register_dep` ([#238](https://github.com/rust-lang/cmake-rs/pull/238)) 11 | 12 | ## [0.1.53](https://github.com/rust-lang/cmake-rs/compare/v0.1.52...v0.1.53) - 2025-01-27 13 | 14 | ### Other 15 | 16 | - Disable broken Make jobserver support on OSX to fix parallel builds ([#229](https://github.com/rust-lang/cmake-rs/pull/229)) 17 | 18 | ## [0.1.52](https://github.com/rust-lang/cmake-rs/compare/v0.1.51...v0.1.52) - 2024-11-25 19 | 20 | ### Other 21 | 22 | - Expose cc-rs no_default_flags for hassle-free cross-compilation ([#225](https://github.com/rust-lang/cmake-rs/pull/225)) 23 | - Add a `success` job to CI 24 | - Change `--build` to use an absolute path 25 | - Merge pull request [#195](https://github.com/rust-lang/cmake-rs/pull/195) from meowtec/feat/improve-fail-hint 26 | - Improve hint for cmake not installed in Linux (code 127) 27 | 28 | ## [0.1.51](https://github.com/rust-lang/cmake-rs/compare/v0.1.50...v0.1.51) - 2024-08-15 29 | 30 | ### Added 31 | 32 | - Add JOM generator support ([#183](https://github.com/rust-lang/cmake-rs/pull/183)) 33 | - Improve visionOS support ([#209](https://github.com/rust-lang/cmake-rs/pull/209)) 34 | - Use `Generic` for bare-metal systems ([#187](https://github.com/rust-lang/cmake-rs/pull/187)) 35 | 36 | ### Fixed 37 | 38 | - Fix cross compilation on android armv7 and x86 ([#186](https://github.com/rust-lang/cmake-rs/pull/186)) 39 | 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cmake" 3 | version = "0.1.54" 4 | authors = ["Alex Crichton "] 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | keywords = ["build-dependencies"] 8 | repository = "https://github.com/rust-lang/cmake-rs" 9 | homepage = "https://github.com/rust-lang/cmake-rs" 10 | documentation = "https://docs.rs/cmake" 11 | description = """ 12 | A build dependency for running `cmake` to build a native library 13 | """ 14 | categories = ["development-tools::build-utils"] 15 | edition = "2021" 16 | 17 | [dependencies] 18 | cc = "1.1.0" 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Alex Crichton 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 | # cmake 2 | 3 | [Documentation](https://docs.rs/cmake) 4 | 5 | A build dependency for running the `cmake` build tool to compile a native 6 | library. 7 | 8 | ```toml 9 | # Cargo.toml 10 | [build-dependencies] 11 | cmake = "0.1" 12 | ``` 13 | 14 | The CMake executable is assumed to be `cmake` unless the `CMAKE` 15 | environmental variable is set. 16 | 17 | # License 18 | 19 | This project is licensed under either of 20 | 21 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 22 | https://www.apache.org/licenses/LICENSE-2.0) 23 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 24 | https://opensource.org/licenses/MIT) 25 | 26 | at your option. 27 | 28 | ### Contribution 29 | 30 | Unless you explicitly state otherwise, any contribution intentionally submitted 31 | for inclusion in cmake by you, as defined in the Apache-2.0 license, shall be 32 | dual licensed as above, without any additional terms or conditions. 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A build dependency for running `cmake` to build a native library 2 | //! 3 | //! This crate provides some necessary boilerplate and shim support for running 4 | //! the system `cmake` command to build a native library. It will add 5 | //! appropriate cflags for building code to link into Rust, handle cross 6 | //! compilation, and use the necessary generator for the platform being 7 | //! targeted. 8 | //! 9 | //! The builder-style configuration allows for various variables and such to be 10 | //! passed down into the build as well. 11 | //! 12 | //! ## Installation 13 | //! 14 | //! Add this to your `Cargo.toml`: 15 | //! 16 | //! ```toml 17 | //! [build-dependencies] 18 | //! cmake = "0.1" 19 | //! ``` 20 | //! 21 | //! ## Examples 22 | //! 23 | //! ```no_run 24 | //! use cmake; 25 | //! 26 | //! // Builds the project in the directory located in `libfoo`, installing it 27 | //! // into $OUT_DIR 28 | //! let dst = cmake::build("libfoo"); 29 | //! 30 | //! println!("cargo:rustc-link-search=native={}", dst.display()); 31 | //! println!("cargo:rustc-link-lib=static=foo"); 32 | //! ``` 33 | //! 34 | //! ```no_run 35 | //! use cmake::Config; 36 | //! 37 | //! let dst = Config::new("libfoo") 38 | //! .define("FOO", "BAR") 39 | //! .cflag("-foo") 40 | //! .build(); 41 | //! println!("cargo:rustc-link-search=native={}", dst.display()); 42 | //! println!("cargo:rustc-link-lib=static=foo"); 43 | //! ``` 44 | 45 | #![deny(missing_docs)] 46 | 47 | extern crate cc; 48 | 49 | use std::collections::HashMap; 50 | use std::env; 51 | use std::ffi::{OsStr, OsString}; 52 | use std::fs::{self, File}; 53 | use std::io::prelude::*; 54 | use std::io::ErrorKind; 55 | use std::path::{Path, PathBuf}; 56 | use std::process::Command; 57 | 58 | /// Builder style configuration for a pending CMake build. 59 | pub struct Config { 60 | path: PathBuf, 61 | generator: Option, 62 | generator_toolset: Option, 63 | cflags: OsString, 64 | cxxflags: OsString, 65 | asmflags: OsString, 66 | defines: Vec<(OsString, OsString)>, 67 | deps: Vec, 68 | target: Option, 69 | host: Option, 70 | out_dir: Option, 71 | profile: Option, 72 | configure_args: Vec, 73 | build_args: Vec, 74 | cmake_target: Option, 75 | env: Vec<(OsString, OsString)>, 76 | static_crt: Option, 77 | uses_cxx11: bool, 78 | always_configure: bool, 79 | no_build_target: bool, 80 | no_default_flags: bool, 81 | verbose_cmake: bool, 82 | verbose_make: bool, 83 | pic: Option, 84 | c_cfg: Option, 85 | cxx_cfg: Option, 86 | env_cache: HashMap>, 87 | } 88 | 89 | /// Builds the native library rooted at `path` with the default cmake options. 90 | /// This will return the directory in which the library was installed. 91 | /// 92 | /// # Examples 93 | /// 94 | /// ```no_run 95 | /// use cmake; 96 | /// 97 | /// // Builds the project in the directory located in `libfoo`, installing it 98 | /// // into $OUT_DIR 99 | /// let dst = cmake::build("libfoo"); 100 | /// 101 | /// println!("cargo:rustc-link-search=native={}", dst.display()); 102 | /// println!("cargo:rustc-link-lib=static=foo"); 103 | /// ``` 104 | /// 105 | pub fn build>(path: P) -> PathBuf { 106 | Config::new(path.as_ref()).build() 107 | } 108 | 109 | impl Config { 110 | /// Return explicitly set profile or infer `CMAKE_BUILD_TYPE` from Rust's compilation profile. 111 | /// 112 | /// * if `opt-level=0` then `CMAKE_BUILD_TYPE=Debug`, 113 | /// * if `opt-level={1,2,3}` and: 114 | /// * `debug=false` then `CMAKE_BUILD_TYPE=Release` 115 | /// * otherwise `CMAKE_BUILD_TYPE=RelWithDebInfo` 116 | /// * if `opt-level={s,z}` then `CMAKE_BUILD_TYPE=MinSizeRel` 117 | pub fn get_profile(&self) -> &str { 118 | if let Some(profile) = self.profile.as_ref() { 119 | profile 120 | } else { 121 | // Determine Rust's profile, optimization level, and debug info: 122 | #[derive(PartialEq)] 123 | enum RustProfile { 124 | Debug, 125 | Release, 126 | } 127 | #[derive(PartialEq, Debug)] 128 | enum OptLevel { 129 | Debug, 130 | Release, 131 | Size, 132 | } 133 | 134 | let rust_profile = match &getenv_unwrap("PROFILE")[..] { 135 | "debug" => RustProfile::Debug, 136 | "release" | "bench" => RustProfile::Release, 137 | unknown => { 138 | eprintln!( 139 | "Warning: unknown Rust profile={}; defaulting to a release build.", 140 | unknown 141 | ); 142 | RustProfile::Release 143 | } 144 | }; 145 | 146 | let opt_level = match &getenv_unwrap("OPT_LEVEL")[..] { 147 | "0" => OptLevel::Debug, 148 | "1" | "2" | "3" => OptLevel::Release, 149 | "s" | "z" => OptLevel::Size, 150 | unknown => { 151 | let default_opt_level = match rust_profile { 152 | RustProfile::Debug => OptLevel::Debug, 153 | RustProfile::Release => OptLevel::Release, 154 | }; 155 | eprintln!( 156 | "Warning: unknown opt-level={}; defaulting to a {:?} build.", 157 | unknown, default_opt_level 158 | ); 159 | default_opt_level 160 | } 161 | }; 162 | 163 | let debug_info: bool = match &getenv_unwrap("DEBUG")[..] { 164 | "false" => false, 165 | "true" => true, 166 | unknown => { 167 | eprintln!("Warning: unknown debug={}; defaulting to `true`.", unknown); 168 | true 169 | } 170 | }; 171 | 172 | match (opt_level, debug_info) { 173 | (OptLevel::Debug, _) => "Debug", 174 | (OptLevel::Release, false) => "Release", 175 | (OptLevel::Release, true) => "RelWithDebInfo", 176 | (OptLevel::Size, _) => "MinSizeRel", 177 | } 178 | } 179 | } 180 | 181 | /// Creates a new blank set of configuration to build the project specified 182 | /// at the path `path`. 183 | pub fn new>(path: P) -> Config { 184 | Config { 185 | path: env::current_dir().unwrap().join(path), 186 | generator: None, 187 | generator_toolset: None, 188 | no_default_flags: false, 189 | cflags: OsString::new(), 190 | cxxflags: OsString::new(), 191 | asmflags: OsString::new(), 192 | defines: Vec::new(), 193 | deps: Vec::new(), 194 | profile: None, 195 | out_dir: None, 196 | target: None, 197 | host: None, 198 | configure_args: Vec::new(), 199 | build_args: Vec::new(), 200 | cmake_target: None, 201 | env: Vec::new(), 202 | static_crt: None, 203 | uses_cxx11: false, 204 | always_configure: true, 205 | no_build_target: false, 206 | verbose_cmake: false, 207 | verbose_make: false, 208 | pic: None, 209 | c_cfg: None, 210 | cxx_cfg: None, 211 | env_cache: HashMap::new(), 212 | } 213 | } 214 | 215 | /// Sets flag for PIC. Otherwise use cc::Build platform default 216 | pub fn pic(&mut self, explicit_flag: bool) -> &mut Config { 217 | self.pic = Some(explicit_flag); 218 | self 219 | } 220 | 221 | /// Sets the build-tool generator (`-G`) for this compilation. 222 | /// 223 | /// If unset, this crate will use the `CMAKE_GENERATOR` environment variable 224 | /// if set. Otherwise, it will guess the best generator to use based on the 225 | /// build target. 226 | pub fn generator>(&mut self, generator: T) -> &mut Config { 227 | self.generator = Some(generator.as_ref().to_owned()); 228 | self 229 | } 230 | 231 | /// Sets the toolset name (-T) if supported by generator. 232 | /// Can be used to compile with Clang/LLVM instead of msvc when Visual Studio generator is selected. 233 | /// 234 | /// If unset, will use the default toolset of the selected generator. 235 | pub fn generator_toolset>(&mut self, toolset_name: T) -> &mut Config { 236 | self.generator_toolset = Some(toolset_name.as_ref().to_owned()); 237 | self 238 | } 239 | 240 | /// Adds a custom flag to pass down to the C compiler, supplementing those 241 | /// that this library already passes. 242 | pub fn cflag>(&mut self, flag: P) -> &mut Config { 243 | self.cflags.push(" "); 244 | self.cflags.push(flag.as_ref()); 245 | self 246 | } 247 | 248 | /// Adds a custom flag to pass down to the C++ compiler, supplementing those 249 | /// that this library already passes. 250 | pub fn cxxflag>(&mut self, flag: P) -> &mut Config { 251 | self.cxxflags.push(" "); 252 | self.cxxflags.push(flag.as_ref()); 253 | self 254 | } 255 | 256 | /// Adds a custom flag to pass down to the ASM compiler, supplementing those 257 | /// that this library already passes. 258 | pub fn asmflag>(&mut self, flag: P) -> &mut Config { 259 | self.asmflags.push(" "); 260 | self.asmflags.push(flag.as_ref()); 261 | self 262 | } 263 | 264 | /// Adds a new `-D` flag to pass to cmake during the generation step. 265 | pub fn define(&mut self, k: K, v: V) -> &mut Config 266 | where 267 | K: AsRef, 268 | V: AsRef, 269 | { 270 | self.defines 271 | .push((k.as_ref().to_owned(), v.as_ref().to_owned())); 272 | self 273 | } 274 | 275 | /// Registers a dependency for this compilation on the native library built 276 | /// by Cargo previously. 277 | /// 278 | /// This registration will update the `CMAKE_PREFIX_PATH` environment 279 | /// variable for the [`build`][Self::build] system generation step. The 280 | /// path will be updated to include the content of the environment 281 | /// variable `DEP_XXX_ROOT`, where `XXX` is replaced with the uppercased 282 | /// value of `dep` (if that variable exists). 283 | pub fn register_dep(&mut self, dep: &str) -> &mut Config { 284 | self.deps.push(dep.to_string()); 285 | self 286 | } 287 | 288 | /// Sets the target triple for this compilation. 289 | /// 290 | /// This is automatically scraped from `$TARGET` which is set for Cargo 291 | /// build scripts so it's not necessary to call this from a build script. 292 | pub fn target(&mut self, target: &str) -> &mut Config { 293 | self.target = Some(target.to_string()); 294 | self 295 | } 296 | 297 | /// Disables the cmake target option for this compilation. 298 | /// 299 | /// Note that this isn't related to the target triple passed to the compiler! 300 | pub fn no_build_target(&mut self, no_build_target: bool) -> &mut Config { 301 | self.no_build_target = no_build_target; 302 | self 303 | } 304 | 305 | /// Disables the generation of default compiler flags. The default compiler 306 | /// flags may cause conflicts in some cross compiling scenarios. 307 | pub fn no_default_flags(&mut self, no_default_flags: bool) -> &mut Config { 308 | self.no_default_flags = no_default_flags; 309 | self 310 | } 311 | 312 | /// Sets the host triple for this compilation. 313 | /// 314 | /// This is automatically scraped from `$HOST` which is set for Cargo 315 | /// build scripts so it's not necessary to call this from a build script. 316 | pub fn host(&mut self, host: &str) -> &mut Config { 317 | self.host = Some(host.to_string()); 318 | self 319 | } 320 | 321 | /// Sets the output directory for this compilation. 322 | /// 323 | /// This is automatically scraped from `$OUT_DIR` which is set for Cargo 324 | /// build scripts so it's not necessary to call this from a build script. 325 | pub fn out_dir>(&mut self, out: P) -> &mut Config { 326 | self.out_dir = Some(out.as_ref().to_path_buf()); 327 | self 328 | } 329 | 330 | /// Sets the `CMAKE_BUILD_TYPE=build_type` variable. 331 | /// 332 | /// By default, this value is automatically inferred from Rust's compilation 333 | /// profile as follows: 334 | /// 335 | /// * if `opt-level=0` then `CMAKE_BUILD_TYPE=Debug`, 336 | /// * if `opt-level={1,2,3}` and: 337 | /// * `debug=false` then `CMAKE_BUILD_TYPE=Release` 338 | /// * otherwise `CMAKE_BUILD_TYPE=RelWithDebInfo` 339 | /// * if `opt-level={s,z}` then `CMAKE_BUILD_TYPE=MinSizeRel` 340 | pub fn profile(&mut self, profile: &str) -> &mut Config { 341 | self.profile = Some(profile.to_string()); 342 | self 343 | } 344 | 345 | /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. 346 | /// 347 | /// This option defaults to `false`, and affect only msvc targets. 348 | pub fn static_crt(&mut self, static_crt: bool) -> &mut Config { 349 | self.static_crt = Some(static_crt); 350 | self 351 | } 352 | 353 | /// Add an argument to the `cmake` configure step 354 | pub fn configure_arg>(&mut self, arg: A) -> &mut Config { 355 | self.configure_args.push(arg.as_ref().to_owned()); 356 | self 357 | } 358 | 359 | /// Add an argument to the final `cmake` build step 360 | pub fn build_arg>(&mut self, arg: A) -> &mut Config { 361 | self.build_args.push(arg.as_ref().to_owned()); 362 | self 363 | } 364 | 365 | /// Configure an environment variable for the `cmake` processes spawned by 366 | /// this crate in the `build` step. 367 | pub fn env(&mut self, key: K, value: V) -> &mut Config 368 | where 369 | K: AsRef, 370 | V: AsRef, 371 | { 372 | self.env 373 | .push((key.as_ref().to_owned(), value.as_ref().to_owned())); 374 | self 375 | } 376 | 377 | /// Sets the build target for the final `cmake` build step, this will 378 | /// default to "install" if not specified. 379 | pub fn build_target(&mut self, target: &str) -> &mut Config { 380 | self.cmake_target = Some(target.to_string()); 381 | self 382 | } 383 | 384 | /// Alters the default target triple on OSX to ensure that c++11 is 385 | /// available. Does not change the target triple if it is explicitly 386 | /// specified. 387 | /// 388 | /// This does not otherwise affect any CXX flags, i.e. it does not set 389 | /// -std=c++11 or -stdlib=libc++. 390 | #[deprecated = "no longer does anything, C++ is determined based on `cc::Build`, and the macOS issue has been fixed upstream"] 391 | pub fn uses_cxx11(&mut self) -> &mut Config { 392 | self.uses_cxx11 = true; 393 | self 394 | } 395 | 396 | /// Forces CMake to always run before building the custom target. 397 | /// 398 | /// In some cases, when you have a big project, you can disable 399 | /// subsequents runs of cmake to make `cargo build` faster. 400 | pub fn always_configure(&mut self, always_configure: bool) -> &mut Config { 401 | self.always_configure = always_configure; 402 | self 403 | } 404 | 405 | /// Sets very verbose output. 406 | pub fn very_verbose(&mut self, value: bool) -> &mut Config { 407 | self.verbose_cmake = value; 408 | self.verbose_make = value; 409 | self 410 | } 411 | 412 | // Simple heuristic to determine if we're cross-compiling using the Android 413 | // NDK toolchain file. 414 | fn uses_android_ndk(&self) -> bool { 415 | // `ANDROID_ABI` is the only required flag: 416 | // https://developer.android.com/ndk/guides/cmake#android_abi 417 | self.defined("ANDROID_ABI") 418 | && self.defines.iter().any(|(flag, value)| { 419 | flag == "CMAKE_TOOLCHAIN_FILE" 420 | && Path::new(value).file_name() == Some("android.toolchain.cmake".as_ref()) 421 | }) 422 | } 423 | 424 | /// Initializes the C build configuration. 425 | pub fn init_c_cfg(&mut self, c_cfg: cc::Build) -> &mut Config { 426 | self.c_cfg = Some(c_cfg); 427 | self 428 | } 429 | 430 | /// Initializes the C++ build configuration. 431 | pub fn init_cxx_cfg(&mut self, cxx_cfg: cc::Build) -> &mut Config { 432 | self.cxx_cfg = Some(cxx_cfg); 433 | self 434 | } 435 | 436 | /// Run this configuration, compiling the library with all the configured 437 | /// options. 438 | /// 439 | /// This will run both the build system generator command as well as the 440 | /// command to build the library. 441 | pub fn build(&mut self) -> PathBuf { 442 | let target = match self.target.clone() { 443 | Some(t) => t, 444 | None => getenv_unwrap("TARGET"), 445 | }; 446 | let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST")); 447 | 448 | // Some decisions later on are made if CMAKE_TOOLCHAIN_FILE is defined, 449 | // so we need to read it from the environment variables from the beginning. 450 | if !self.defined("CMAKE_TOOLCHAIN_FILE") { 451 | if let Some(s) = self.getenv_target_os("CMAKE_TOOLCHAIN_FILE") { 452 | self.define("CMAKE_TOOLCHAIN_FILE", s); 453 | } else if target.contains("redox") { 454 | if !self.defined("CMAKE_SYSTEM_NAME") { 455 | self.define("CMAKE_SYSTEM_NAME", "Generic"); 456 | } 457 | } else if target != host && !self.defined("CMAKE_SYSTEM_NAME") { 458 | // Set CMAKE_SYSTEM_NAME and CMAKE_SYSTEM_PROCESSOR when cross compiling 459 | let os = getenv_unwrap("CARGO_CFG_TARGET_OS"); 460 | let arch = getenv_unwrap("CARGO_CFG_TARGET_ARCH"); 461 | // CMAKE_SYSTEM_NAME list 462 | // https://gitlab.kitware.com/cmake/cmake/-/issues/21489#note_1077167 463 | // 464 | // CMAKE_SYSTEM_PROCESSOR 465 | // some of the values come from https://en.wikipedia.org/wiki/Uname 466 | let (system_name, system_processor) = match (os.as_str(), arch.as_str()) { 467 | ("android", "arm") => ("Android", "armv7-a"), 468 | ("android", "x86") => ("Android", "i686"), 469 | ("android", arch) => ("Android", arch), 470 | ("dragonfly", arch) => ("DragonFly", arch), 471 | ("macos", "aarch64") => ("Darwin", "arm64"), 472 | ("macos", arch) => ("Darwin", arch), 473 | ("freebsd", "x86_64") => ("FreeBSD", "amd64"), 474 | ("freebsd", arch) => ("FreeBSD", arch), 475 | ("fuchsia", arch) => ("Fuchsia", arch), 476 | ("haiku", arch) => ("Haiku", arch), 477 | ("ios", "aarch64") => ("iOS", "arm64"), 478 | ("ios", arch) => ("iOS", arch), 479 | ("linux", arch) => { 480 | let name = "Linux"; 481 | match arch { 482 | "powerpc" => (name, "ppc"), 483 | "powerpc64" => (name, "ppc64"), 484 | "powerpc64le" => (name, "ppc64le"), 485 | _ => (name, arch), 486 | } 487 | } 488 | ("netbsd", arch) => ("NetBSD", arch), 489 | ("openbsd", "x86_64") => ("OpenBSD", "amd64"), 490 | ("openbsd", arch) => ("OpenBSD", arch), 491 | ("solaris", arch) => ("SunOS", arch), 492 | ("tvos", "aarch64") => ("tvOS", "arm64"), 493 | ("tvos", arch) => ("tvOS", arch), 494 | ("visionos", "aarch64") => ("visionOS", "arm64"), 495 | ("visionos", arch) => ("visionOS", arch), 496 | ("watchos", "aarch64") => ("watchOS", "arm64"), 497 | ("watchos", arch) => ("watchOS", arch), 498 | ("windows", "x86_64") => ("Windows", "AMD64"), 499 | ("windows", "x86") => ("Windows", "X86"), 500 | ("windows", "aarch64") => ("Windows", "ARM64"), 501 | ("none", arch) => ("Generic", arch), 502 | // Others 503 | (os, arch) => (os, arch), 504 | }; 505 | self.define("CMAKE_SYSTEM_NAME", system_name); 506 | self.define("CMAKE_SYSTEM_PROCESSOR", system_processor); 507 | } 508 | } 509 | 510 | let generator = self 511 | .generator 512 | .clone() 513 | .or_else(|| self.getenv_target_os("CMAKE_GENERATOR")); 514 | 515 | let msvc = target.contains("msvc"); 516 | let ndk = self.uses_android_ndk(); 517 | let mut c_cfg = self.c_cfg.clone().unwrap_or_default(); 518 | c_cfg 519 | .cargo_metadata(false) 520 | .cpp(false) 521 | .opt_level(0) 522 | .debug(false) 523 | .warnings(false) 524 | .host(&host) 525 | .no_default_flags(ndk || self.no_default_flags); 526 | if !ndk { 527 | c_cfg.target(&target); 528 | } 529 | let mut cxx_cfg = self.cxx_cfg.clone().unwrap_or_default(); 530 | cxx_cfg 531 | .cargo_metadata(false) 532 | .cpp(true) 533 | .opt_level(0) 534 | .debug(false) 535 | .warnings(false) 536 | .host(&host) 537 | .no_default_flags(ndk || self.no_default_flags); 538 | if !ndk { 539 | cxx_cfg.target(&target); 540 | } 541 | if let Some(static_crt) = self.static_crt { 542 | c_cfg.static_crt(static_crt); 543 | cxx_cfg.static_crt(static_crt); 544 | } 545 | if let Some(explicit_flag) = self.pic { 546 | c_cfg.pic(explicit_flag); 547 | cxx_cfg.pic(explicit_flag); 548 | } 549 | let c_compiler = c_cfg.get_compiler(); 550 | let cxx_compiler = cxx_cfg.get_compiler(); 551 | let asm_compiler = c_cfg.get_compiler(); 552 | 553 | let dst = self 554 | .out_dir 555 | .clone() 556 | .unwrap_or_else(|| PathBuf::from(getenv_unwrap("OUT_DIR"))); 557 | let build = dst.join("build"); 558 | self.maybe_clear(&build); 559 | let _ = fs::create_dir_all(&build); 560 | 561 | // Add all our dependencies to our cmake paths 562 | let mut cmake_prefix_path = Vec::new(); 563 | for dep in &self.deps { 564 | let dep = dep.to_uppercase().replace('-', "_"); 565 | if let Some(root) = env::var_os(format!("DEP_{}_ROOT", dep)) { 566 | cmake_prefix_path.push(PathBuf::from(root)); 567 | } 568 | } 569 | let system_prefix = self 570 | .getenv_target_os("CMAKE_PREFIX_PATH") 571 | .unwrap_or_default(); 572 | cmake_prefix_path.extend(env::split_paths(&system_prefix)); 573 | let cmake_prefix_path = env::join_paths(&cmake_prefix_path).unwrap(); 574 | 575 | // Build up the first cmake command to build the build system. 576 | let mut cmd = self.cmake_configure_command(&target); 577 | 578 | let version = Version::from_command(cmd.get_program()).unwrap_or_default(); 579 | 580 | if self.verbose_cmake { 581 | cmd.arg("-Wdev"); 582 | cmd.arg("--debug-output"); 583 | } 584 | 585 | cmd.arg(&self.path).current_dir(&build); 586 | let mut is_ninja = false; 587 | if let Some(ref generator) = generator { 588 | is_ninja = generator.to_string_lossy().contains("Ninja"); 589 | } 590 | if target.contains("windows-gnu") { 591 | if host.contains("windows") { 592 | // On MinGW we need to coerce cmake to not generate a visual 593 | // studio build system but instead use makefiles that MinGW can 594 | // use to build. 595 | if generator.is_none() { 596 | // If make.exe isn't found, that means we may be using a MinGW 597 | // toolchain instead of a MSYS2 toolchain. If neither is found, 598 | // the build cannot continue. 599 | let has_msys2 = Command::new("make") 600 | .arg("--version") 601 | .output() 602 | .err() 603 | .map(|e| e.kind() != ErrorKind::NotFound) 604 | .unwrap_or(true); 605 | let has_mingw32 = Command::new("mingw32-make") 606 | .arg("--version") 607 | .output() 608 | .err() 609 | .map(|e| e.kind() != ErrorKind::NotFound) 610 | .unwrap_or(true); 611 | 612 | let generator = match (has_msys2, has_mingw32) { 613 | (true, _) => "MSYS Makefiles", 614 | (false, true) => "MinGW Makefiles", 615 | (false, false) => fail("no valid generator found for GNU toolchain; MSYS or MinGW must be installed") 616 | }; 617 | 618 | cmd.arg("-G").arg(generator); 619 | } 620 | } else { 621 | // If we're cross compiling onto windows, then set some 622 | // variables which will hopefully get things to succeed. Some 623 | // systems may need the `windres` or `dlltool` variables set, so 624 | // set them if possible. 625 | if !self.defined("CMAKE_RC_COMPILER") { 626 | let exe = find_exe(c_compiler.path()); 627 | if let Some(name) = exe.file_name().unwrap().to_str() { 628 | let name = name.replace("gcc", "windres"); 629 | let windres = exe.with_file_name(name); 630 | if windres.is_file() { 631 | let mut arg = OsString::from("-DCMAKE_RC_COMPILER="); 632 | arg.push(&windres); 633 | cmd.arg(arg); 634 | } 635 | } 636 | } 637 | } 638 | } else if msvc { 639 | // If we're on MSVC we need to be sure to use the right generator or 640 | // otherwise we won't get 32/64 bit correct automatically. 641 | // This also guarantees that NMake generator isn't chosen implicitly. 642 | let using_nmake_generator = if let Some(g) = &generator { 643 | g == "NMake Makefiles" || g == "NMake Makefiles JOM" 644 | } else { 645 | cmd.arg("-G").arg(self.visual_studio_generator(&target)); 646 | false 647 | }; 648 | if !is_ninja && !using_nmake_generator { 649 | if target.contains("x86_64") { 650 | if self.generator_toolset.is_none() { 651 | cmd.arg("-Thost=x64"); 652 | } 653 | cmd.arg("-Ax64"); 654 | } else if target.contains("thumbv7a") { 655 | if self.generator_toolset.is_none() { 656 | cmd.arg("-Thost=x64"); 657 | } 658 | cmd.arg("-Aarm"); 659 | } else if target.contains("aarch64") { 660 | if self.generator_toolset.is_none() { 661 | cmd.arg("-Thost=x64"); 662 | } 663 | cmd.arg("-AARM64"); 664 | } else if target.contains("i686") { 665 | if self.generator_toolset.is_none() { 666 | cmd.arg("-Thost=x86"); 667 | } 668 | cmd.arg("-AWin32"); 669 | } else { 670 | panic!("unsupported msvc target: {}", target); 671 | } 672 | } 673 | } else if target.contains("darwin") && !self.defined("CMAKE_OSX_ARCHITECTURES") { 674 | if target.contains("x86_64") { 675 | cmd.arg("-DCMAKE_OSX_ARCHITECTURES=x86_64"); 676 | } else if target.contains("aarch64") { 677 | cmd.arg("-DCMAKE_OSX_ARCHITECTURES=arm64"); 678 | } else { 679 | panic!("unsupported darwin target: {}", target); 680 | } 681 | } 682 | if let Some(ref generator) = generator { 683 | cmd.arg("-G").arg(generator); 684 | } 685 | if let Some(ref generator_toolset) = self.generator_toolset { 686 | cmd.arg("-T").arg(generator_toolset); 687 | } 688 | let profile = self.get_profile().to_string(); 689 | for (k, v) in &self.defines { 690 | let mut os = OsString::from("-D"); 691 | os.push(k); 692 | os.push("="); 693 | os.push(v); 694 | cmd.arg(os); 695 | } 696 | 697 | if !self.defined("CMAKE_INSTALL_PREFIX") { 698 | let mut dstflag = OsString::from("-DCMAKE_INSTALL_PREFIX="); 699 | dstflag.push(&dst); 700 | cmd.arg(dstflag); 701 | } 702 | 703 | let build_type = self 704 | .defines 705 | .iter() 706 | .find(|&(a, _)| a == "CMAKE_BUILD_TYPE") 707 | .map(|x| x.1.to_str().unwrap()) 708 | .unwrap_or(&profile); 709 | let build_type_upcase = build_type 710 | .chars() 711 | .flat_map(|c| c.to_uppercase()) 712 | .collect::(); 713 | 714 | { 715 | // let cmake deal with optimization/debuginfo 716 | let skip_arg = |arg: &OsStr| match arg.to_str() { 717 | Some(s) => s.starts_with("-O") || s.starts_with("/O") || s == "-g", 718 | None => false, 719 | }; 720 | let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| { 721 | let flag_var = format!("CMAKE_{}_FLAGS", kind); 722 | let tool_var = format!("CMAKE_{}_COMPILER", kind); 723 | if !self.defined(&flag_var) { 724 | let mut flagsflag = OsString::from("-D"); 725 | flagsflag.push(&flag_var); 726 | flagsflag.push("="); 727 | flagsflag.push(extra); 728 | for arg in compiler.args() { 729 | if skip_arg(arg) { 730 | continue; 731 | } 732 | flagsflag.push(" "); 733 | flagsflag.push(arg); 734 | } 735 | cmd.arg(flagsflag); 736 | } 737 | 738 | // The visual studio generator apparently doesn't respect 739 | // `CMAKE_C_FLAGS` but does respect `CMAKE_C_FLAGS_RELEASE` and 740 | // such. We need to communicate /MD vs /MT, so set those vars 741 | // here. 742 | // 743 | // Note that for other generators, though, this *overrides* 744 | // things like the optimization flags, which is bad. 745 | if generator.is_none() && msvc { 746 | let flag_var_alt = format!("CMAKE_{}_FLAGS_{}", kind, build_type_upcase); 747 | if !self.defined(&flag_var_alt) { 748 | let mut flagsflag = OsString::from("-D"); 749 | flagsflag.push(&flag_var_alt); 750 | flagsflag.push("="); 751 | flagsflag.push(extra); 752 | for arg in compiler.args() { 753 | if skip_arg(arg) { 754 | continue; 755 | } 756 | flagsflag.push(" "); 757 | flagsflag.push(arg); 758 | } 759 | cmd.arg(flagsflag); 760 | } 761 | } 762 | 763 | // Apparently cmake likes to have an absolute path to the 764 | // compiler as otherwise it sometimes thinks that this variable 765 | // changed as it thinks the found compiler, /usr/bin/cc, 766 | // differs from the specified compiler, cc. Not entirely sure 767 | // what's up, but at least this means cmake doesn't get 768 | // confused? 769 | // 770 | // Also specify this on Windows only if we use MSVC with Ninja, 771 | // as it's not needed for MSVC with Visual Studio generators and 772 | // for MinGW it doesn't really vary. 773 | if !self.defined("CMAKE_TOOLCHAIN_FILE") 774 | && !self.defined(&tool_var) 775 | && (env::consts::FAMILY != "windows" || (msvc && is_ninja)) 776 | { 777 | let mut ccompiler = OsString::from("-D"); 778 | ccompiler.push(&tool_var); 779 | ccompiler.push("="); 780 | ccompiler.push(find_exe(compiler.path())); 781 | #[cfg(windows)] 782 | { 783 | // CMake doesn't like unescaped `\`s in compiler paths 784 | // so we either have to escape them or replace with `/`s. 785 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; 786 | let wchars = ccompiler 787 | .encode_wide() 788 | .map(|wchar| { 789 | if wchar == b'\\' as u16 { 790 | '/' as u16 791 | } else { 792 | wchar 793 | } 794 | }) 795 | .collect::>(); 796 | ccompiler = OsString::from_wide(&wchars); 797 | } 798 | cmd.arg(ccompiler); 799 | } 800 | }; 801 | 802 | set_compiler("C", &c_compiler, &self.cflags); 803 | set_compiler("CXX", &cxx_compiler, &self.cxxflags); 804 | set_compiler("ASM", &asm_compiler, &self.asmflags); 805 | } 806 | 807 | if !self.defined("CMAKE_BUILD_TYPE") { 808 | cmd.arg(format!("-DCMAKE_BUILD_TYPE={}", profile)); 809 | } 810 | 811 | if self.verbose_make { 812 | cmd.arg("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"); 813 | } 814 | 815 | for (k, v) in c_compiler.env().iter().chain(&self.env) { 816 | cmd.env(k, v); 817 | } 818 | 819 | if self.always_configure || !build.join("CMakeCache.txt").exists() { 820 | cmd.args(&self.configure_args); 821 | run(cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), "cmake"); 822 | } else { 823 | println!("CMake project was already configured. Skipping configuration step."); 824 | } 825 | 826 | // And build! 827 | let mut cmd = self.cmake_build_command(&target); 828 | cmd.current_dir(&build); 829 | 830 | for (k, v) in c_compiler.env().iter().chain(&self.env) { 831 | cmd.env(k, v); 832 | } 833 | 834 | // If the generated project is Makefile based we should carefully transfer corresponding CARGO_MAKEFLAGS 835 | let mut use_jobserver = false; 836 | if fs::metadata(build.join("Makefile")).is_ok() { 837 | match env::var_os("CARGO_MAKEFLAGS") { 838 | // Only do this on non-windows, non-bsd, and non-macos (unless a named pipe 839 | // jobserver is available) 840 | // * On Windows, we could be invoking make instead of 841 | // mingw32-make which doesn't work with our jobserver 842 | // * bsdmake also does not work with our job server 843 | // * On macOS, CMake blocks propagation of the jobserver's file descriptors to make 844 | // However, if the jobserver is based on a named pipe, this will be available to 845 | // the build. 846 | Some(ref makeflags) 847 | if !(cfg!(windows) 848 | || cfg!(target_os = "openbsd") 849 | || cfg!(target_os = "netbsd") 850 | || cfg!(target_os = "freebsd") 851 | || cfg!(target_os = "dragonfly") 852 | || (cfg!(target_os = "macos") 853 | && !uses_named_pipe_jobserver(makeflags))) => 854 | { 855 | use_jobserver = true; 856 | cmd.env("MAKEFLAGS", makeflags); 857 | } 858 | _ => {} 859 | } 860 | } 861 | 862 | cmd.arg("--build").arg(&build); 863 | 864 | if !self.no_build_target { 865 | let target = self 866 | .cmake_target 867 | .clone() 868 | .unwrap_or_else(|| "install".to_string()); 869 | cmd.arg("--target").arg(target); 870 | } 871 | 872 | cmd.arg("--config").arg(&profile); 873 | 874 | // --parallel requires CMake 3.12: 875 | // https://cmake.org/cmake/help/latest/release/3.12.html#command-line 876 | if version >= Version::new(3, 12) && !use_jobserver { 877 | if let Ok(s) = env::var("NUM_JOBS") { 878 | // See https://cmake.org/cmake/help/v3.12/manual/cmake.1.html#build-tool-mode 879 | cmd.arg("--parallel").arg(s); 880 | } 881 | } 882 | 883 | if !&self.build_args.is_empty() { 884 | cmd.arg("--").args(&self.build_args); 885 | } 886 | 887 | run(&mut cmd, "cmake"); 888 | 889 | println!("cargo:root={}", dst.display()); 890 | dst 891 | } 892 | 893 | fn cmake_executable(&mut self) -> OsString { 894 | self.getenv_target_os("CMAKE") 895 | .unwrap_or_else(|| OsString::from("cmake")) 896 | } 897 | 898 | // If we are building for Emscripten, wrap the calls to CMake 899 | // as "emcmake cmake ..." and "emmake cmake --build ...". 900 | // https://emscripten.org/docs/compiling/Building-Projects.html 901 | 902 | fn cmake_configure_command(&mut self, target: &str) -> Command { 903 | if target.contains("emscripten") { 904 | let emcmake = self 905 | .getenv_target_os("EMCMAKE") 906 | .unwrap_or_else(|| OsString::from("emcmake")); 907 | let mut cmd = Command::new(emcmake); 908 | cmd.arg(self.cmake_executable()); 909 | cmd 910 | } else { 911 | Command::new(self.cmake_executable()) 912 | } 913 | } 914 | 915 | fn cmake_build_command(&mut self, target: &str) -> Command { 916 | if target.contains("emscripten") { 917 | let emmake = self 918 | .getenv_target_os("EMMAKE") 919 | .unwrap_or_else(|| OsString::from("emmake")); 920 | let mut cmd = Command::new(emmake); 921 | cmd.arg(self.cmake_executable()); 922 | cmd 923 | } else { 924 | Command::new(self.cmake_executable()) 925 | } 926 | } 927 | 928 | fn getenv_os(&mut self, v: &str) -> Option { 929 | if let Some(val) = self.env_cache.get(v) { 930 | return val.clone(); 931 | } 932 | let r = env::var_os(v); 933 | println!("{} = {:?}", v, r); 934 | self.env_cache.insert(v.to_string(), r.clone()); 935 | r 936 | } 937 | 938 | /// Gets a target-specific environment variable. 939 | fn getenv_target_os(&mut self, var_base: &str) -> Option { 940 | let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST")); 941 | let target = self 942 | .target 943 | .clone() 944 | .unwrap_or_else(|| getenv_unwrap("TARGET")); 945 | 946 | let kind = if host == target { "HOST" } else { "TARGET" }; 947 | let target_u = target.replace('-', "_"); 948 | self.getenv_os(&format!("{}_{}", var_base, target)) 949 | .or_else(|| self.getenv_os(&format!("{}_{}", var_base, target_u))) 950 | .or_else(|| self.getenv_os(&format!("{}_{}", kind, var_base))) 951 | .or_else(|| self.getenv_os(var_base)) 952 | } 953 | 954 | fn visual_studio_generator(&self, target: &str) -> String { 955 | use cc::windows_registry::{find_vs_version, VsVers}; 956 | 957 | let base = match find_vs_version() { 958 | Ok(VsVers::Vs17) => "Visual Studio 17 2022", 959 | Ok(VsVers::Vs16) => "Visual Studio 16 2019", 960 | Ok(VsVers::Vs15) => "Visual Studio 15 2017", 961 | Ok(VsVers::Vs14) => "Visual Studio 14 2015", 962 | // This was deprecated recently (2024-07). Ignore the warning for now. 963 | #[allow(deprecated)] 964 | Ok(VsVers::Vs12) => "Visual Studio 12 2013", 965 | Ok(_) => panic!( 966 | "Visual studio version detected but this crate \ 967 | doesn't know how to generate cmake files for it, \ 968 | can the `cmake` crate be updated?" 969 | ), 970 | Err(msg) => panic!("{}", msg), 971 | }; 972 | if ["i686", "x86_64", "thumbv7a", "aarch64"] 973 | .iter() 974 | .any(|t| target.contains(t)) 975 | { 976 | base.to_string() 977 | } else { 978 | panic!("unsupported msvc target: {}", target); 979 | } 980 | } 981 | 982 | fn defined(&self, var: &str) -> bool { 983 | self.defines.iter().any(|(a, _)| a == var) 984 | } 985 | 986 | // If a cmake project has previously been built (e.g. CMakeCache.txt already 987 | // exists), then cmake will choke if the source directory for the original 988 | // project being built has changed. Detect this situation through the 989 | // `CMAKE_HOME_DIRECTORY` variable that cmake emits and if it doesn't match 990 | // we blow away the build directory and start from scratch (the recommended 991 | // solution apparently [1]). 992 | // 993 | // [1]: https://cmake.org/pipermail/cmake/2012-August/051545.html 994 | fn maybe_clear(&self, dir: &Path) { 995 | // CMake will apparently store canonicalized paths which normally 996 | // isn't relevant to us but we canonicalize it here to ensure 997 | // we're both checking the same thing. 998 | let path = fs::canonicalize(&self.path).unwrap_or_else(|_| self.path.clone()); 999 | let mut f = match File::open(dir.join("CMakeCache.txt")) { 1000 | Ok(f) => f, 1001 | Err(..) => return, 1002 | }; 1003 | let mut u8contents = Vec::new(); 1004 | match f.read_to_end(&mut u8contents) { 1005 | Ok(f) => f, 1006 | Err(..) => return, 1007 | }; 1008 | let contents = String::from_utf8_lossy(&u8contents); 1009 | drop(f); 1010 | for line in contents.lines() { 1011 | if line.starts_with("CMAKE_HOME_DIRECTORY") { 1012 | let needs_cleanup = match line.split('=').next_back() { 1013 | Some(cmake_home) => fs::canonicalize(cmake_home) 1014 | .ok() 1015 | .map(|cmake_home| cmake_home != path) 1016 | .unwrap_or(true), 1017 | None => true, 1018 | }; 1019 | if needs_cleanup { 1020 | println!( 1021 | "detected home dir change, cleaning out entire build \ 1022 | directory" 1023 | ); 1024 | fs::remove_dir_all(dir).unwrap(); 1025 | } 1026 | break; 1027 | } 1028 | } 1029 | } 1030 | } 1031 | 1032 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 1033 | struct Version { 1034 | major: u32, 1035 | minor: u32, 1036 | } 1037 | 1038 | impl Version { 1039 | fn new(major: u32, minor: u32) -> Self { 1040 | Self { major, minor } 1041 | } 1042 | 1043 | fn parse(s: &str) -> Option { 1044 | // As of 3.22, the format of the version output is "cmake version ..". 1045 | // ``` 1046 | // $ cmake --version 1047 | // cmake version 3.22.2 1048 | // 1049 | // CMake suite maintained and supported by Kitware (kitware.com/cmake). 1050 | // ``` 1051 | let version = s.lines().next()?.strip_prefix("cmake version ")?; 1052 | let mut digits = version.splitn(3, '.'); // split version string to major minor patch 1053 | let major = digits.next()?.parse::().ok()?; 1054 | let minor = digits.next()?.parse::().ok()?; 1055 | // Ignore the patch version because it does not change the API. 1056 | Some(Version::new(major, minor)) 1057 | } 1058 | 1059 | fn from_command(executable: &OsStr) -> Option { 1060 | let output = Command::new(executable).arg("--version").output().ok()?; 1061 | if !output.status.success() { 1062 | return None; 1063 | } 1064 | let stdout = core::str::from_utf8(&output.stdout).ok()?; 1065 | Self::parse(stdout) 1066 | } 1067 | } 1068 | 1069 | impl Default for Version { 1070 | fn default() -> Self { 1071 | // If the version parsing fails, we assume that it is the latest known 1072 | // version. This is because the failure of version parsing may be due to 1073 | // the version output being changed. 1074 | Self::new(3, 22) 1075 | } 1076 | } 1077 | 1078 | fn run(cmd: &mut Command, program: &str) { 1079 | println!("running: {:?}", cmd); 1080 | let status = match cmd.status() { 1081 | Ok(status) => status, 1082 | Err(ref e) if e.kind() == ErrorKind::NotFound => { 1083 | fail(&format!( 1084 | "failed to execute command: {}\nis `{}` not installed?", 1085 | e, program 1086 | )); 1087 | } 1088 | Err(e) => fail(&format!("failed to execute command: {}", e)), 1089 | }; 1090 | if !status.success() { 1091 | if status.code() == Some(127) { 1092 | fail(&format!( 1093 | "command did not execute successfully, got: {}, is `{}` not installed?", 1094 | status, program 1095 | )); 1096 | } 1097 | fail(&format!( 1098 | "command did not execute successfully, got: {}", 1099 | status 1100 | )); 1101 | } 1102 | } 1103 | 1104 | fn find_exe(path: &Path) -> PathBuf { 1105 | env::split_paths(&env::var_os("PATH").unwrap_or_default()) 1106 | .map(|p| p.join(path)) 1107 | .find(|p| fs::metadata(p).is_ok()) 1108 | .unwrap_or_else(|| path.to_owned()) 1109 | } 1110 | 1111 | fn getenv_unwrap(v: &str) -> String { 1112 | match env::var(v) { 1113 | Ok(s) => s, 1114 | Err(..) => fail(&format!("environment variable `{}` not defined", v)), 1115 | } 1116 | } 1117 | 1118 | fn fail(s: &str) -> ! { 1119 | panic!("\n{}\n\nbuild script failed, must exit now", s) 1120 | } 1121 | 1122 | /// Returns whether the given MAKEFLAGS indicate that there is an available 1123 | /// jobserver that uses a named pipe (fifo) 1124 | fn uses_named_pipe_jobserver(makeflags: &OsStr) -> bool { 1125 | makeflags 1126 | .to_string_lossy() 1127 | // auth option as defined in 1128 | // https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver 1129 | .contains("--jobserver-auth=fifo:") 1130 | } 1131 | 1132 | #[cfg(test)] 1133 | mod tests { 1134 | use super::uses_named_pipe_jobserver; 1135 | use super::Version; 1136 | 1137 | #[test] 1138 | fn test_cmake_version() { 1139 | let text = "cmake version 3.22.2 1140 | 1141 | CMake suite maintained and supported by Kitware (kitware.com/cmake). 1142 | "; 1143 | let v = Version::parse(text).unwrap(); 1144 | assert_eq!(v, Version::new(3, 22)); 1145 | assert!(Version::new(3, 22) > Version::new(3, 21)); 1146 | assert!(Version::new(3, 22) < Version::new(3, 23)); 1147 | 1148 | let _v = Version::from_command("cmake".as_ref()).unwrap(); 1149 | } 1150 | 1151 | #[test] 1152 | fn test_uses_fifo_jobserver() { 1153 | assert!(uses_named_pipe_jobserver( 1154 | "-j --jobserver-auth=fifo:/foo".as_ref() 1155 | )); 1156 | assert!(!uses_named_pipe_jobserver( 1157 | "-j --jobserver-auth=8:9".as_ref() 1158 | )); 1159 | } 1160 | } 1161 | -------------------------------------------------------------------------------- /test-crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /test-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-crate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | libz-sys = { version = "1.1.19", default-features = false, features = ["zlib-ng"] } 9 | 10 | [patch.crates-io] 11 | cmake = { path = ".." } 12 | -------------------------------------------------------------------------------- /test-crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use libz_sys::zlibVersion; 4 | use std::ffi::CStr; 5 | 6 | #[test] 7 | fn zlib_version() { 8 | let ver = unsafe { zlibVersion() }; 9 | let ver_cstr = unsafe { CStr::from_ptr(ver) }; 10 | let version = ver_cstr.to_str().unwrap(); 11 | assert!(!version.is_empty()); 12 | } 13 | } 14 | --------------------------------------------------------------------------------