diff options
-rw-r--r-- | .cargo_vcs_info.json | 7 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 15 | ||||
-rw-r--r-- | .github/workflows/ssh.yml | 40 | ||||
-rw-r--r-- | Android.bp | 10 | ||||
-rw-r--r-- | CHANGELOG.md | 42 | ||||
-rw-r--r-- | Cargo.toml | 15 | ||||
-rw-r--r-- | Cargo.toml.orig | 6 | ||||
-rw-r--r-- | METADATA | 10 | ||||
-rw-r--r-- | README.md | 101 | ||||
-rw-r--r-- | TEST_MAPPING | 15 | ||||
-rw-r--r-- | build.rs | 29 | ||||
-rw-r--r-- | build/common.rs | 288 | ||||
-rw-r--r-- | build/dynamic.rs | 70 | ||||
-rw-r--r-- | build/static.rs | 90 | ||||
-rw-r--r-- | cargo2android.json | 5 | ||||
-rw-r--r-- | out/common.rs | 288 | ||||
-rw-r--r-- | out/dynamic.rs | 70 | ||||
-rw-r--r-- | src/lib.rs | 114 | ||||
-rw-r--r-- | src/link.rs | 14 | ||||
-rw-r--r-- | src/support.rs | 95 | ||||
-rw-r--r-- | tests/lib.rs | 7 |
21 files changed, 742 insertions, 589 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index b4474d7..ac25bc0 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,6 @@ { "git": { - "sha1": "fae8e5e15f060bc1fb11c26eac168eaf8a50632b" - } -} + "sha1": "8090d6853624a04b859e8ab2e467bae88a8d4ef6" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 993f360..5bd0a2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,24 +15,17 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] - clang: [["11.0", "clang_11_0"]] + clang: [["13.0", "clang_13_0"]] rust: ["1.40.0"] steps: - name: Checkout Repository uses: actions/checkout@v2 # LLVM and Clang - - name: Cache LLVM and Clang - id: cache-llvm - uses: actions/cache@v2 - with: - path: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }} - key: ${{ matrix.os }}-llvm-${{ matrix.clang[0] }} - name: Install LLVM and Clang uses: KyleMayes/install-llvm-action@v1 with: version: ${{ matrix.clang[0] }} directory: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }} - cached: ${{ steps.cache-llvm.outputs.cache-hit }} # Rust - name: Install Rust uses: actions-rs/toolchain@v1 @@ -41,17 +34,11 @@ jobs: # Test - name: Cargo Test (Dynamic) uses: actions-rs/cargo@v1 - env: - LIBCLANG_PATH: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }}/lib - LLVM_CONFIG_PATH: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }}/bin/llvm-config with: command: test args: --verbose --features ${{ matrix.clang[1] }} -- --nocapture - name: Cargo Test (Runtime) uses: actions-rs/cargo@v1 - env: - LIBCLANG_PATH: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }}/lib - LLVM_CONFIG_PATH: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }}/bin/llvm-config with: command: test args: --verbose --features "${{ matrix.clang[1] }} runtime" -- --nocapture diff --git a/.github/workflows/ssh.yml b/.github/workflows/ssh.yml new file mode 100644 index 0000000..5dfc251 --- /dev/null +++ b/.github/workflows/ssh.yml @@ -0,0 +1,40 @@ +name: SSH + +on: + workflow_dispatch: + inputs: + os: + description: "Operating System" + required: true + default: "ubuntu-latest" + +jobs: + ssh: + name: SSH + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + clang: [["13.0", "clang_13_0"]] + rust: ["1.40.0"] + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + if: github.event.inputs.os == matrix.os + # LLVM and Clang + - name: Install LLVM and Clang + uses: KyleMayes/install-llvm-action@v1 + if: github.event.inputs.os == matrix.os + with: + version: ${{ matrix.clang[0] }} + directory: ${{ runner.temp }}/llvm-${{ matrix.clang[0] }} + # Rust + - name: Install Rust + uses: actions-rs/toolchain@v1 + if: github.event.inputs.os == matrix.os + with: + toolchain: ${{ matrix.rust }} + # SSH + - name: Enable SSH + uses: mxschmitt/action-tmate@v3 + if: github.event.inputs.os == matrix.os @@ -1,4 +1,4 @@ -// This file is generated by cargo2android.py --run --features=runtime,clang_10_0 --dependencies --copy-out. +// This file is generated by cargo2android.py --config cargo2android.json. // Do not modify this file as changes will be overridden on upgrade. package { @@ -31,6 +31,8 @@ genrule { rust_library_host { name: "libclang_sys", crate_name: "clang_sys", + cargo_env_compat: true, + cargo_pkg_version: "1.3.1", srcs: [ "src/lib.rs", ":copy_clang-sys_build_out", @@ -58,9 +60,3 @@ rust_library_host { "liblibloading", ], } - -// dependent_library ["feature_list"] -// cfg-if-1.0.0 -// glob-0.3.0 -// libc-0.2.86 -// libloading-0.7.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index a4940fa..681cde4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +## [1.3.1] - 2022-02-03 + +### Added +- Added missing `clang_getToken` function + +## [1.3.0] - 2021-10-31 + +### Added +- Added support for `clang` 13.0.x +- Added support for `clang` 12.0.x +- Added support for the Haiku operating system + +## [1.2.2] - 2021-09-02 + +### Fixed +- Fixed handling of paths that contain characters that have special meaning in +glob patterns (e.g., `[` or `]`) + +## [1.2.1] - 2021-08-24 + +### Changed +- Updated build script to check the install location used by the +[Scoop](https://scoop.sh/) command-line installer on Windows + +### Fixed +- Updated build script to support environments where the `PATH` environment +variable is not set + +## [1.2.0] - 2021-04-08 + +### Changed +- Changed `Clang::find` to prefer target-prefixed binaries when a `-target` +argument is provided (e.g., if the arguments `-target` and +`x86_64-unknown-linux-gnu` are provided, a target-prefixed Clang executable +such as `x86_64-unknown-linux-gnu-clang` will be preferred over a non-target +prefixed Clang executable) + +### Fixed +- Fixed build script to split paths in environment variables (e.g., +`LD_LIBRARY_PATH`) using the appropriate separator for the platform (previously +`:` was used as the separator but some platforms such as Windows use `;`) + ## [1.1.1] - 2021-02-19 ### Changed @@ -3,16 +3,15 @@ # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] name = "clang-sys" -version = "1.1.1" +version = "1.3.1" authors = ["Kyle Mayes <kyle@mayeses.com>"] build = "build.rs" links = "clang" @@ -22,7 +21,7 @@ readme = "README.md" license = "Apache-2.0" repository = "https://github.com/KyleMayes/clang-sys" [package.metadata.docs.rs] -features = ["clang_10_0", "runtime"] +features = ["clang_13_0", "runtime"] [dependencies.glob] version = "0.3" @@ -39,6 +38,8 @@ version = "0.3" [features] clang_10_0 = ["clang_9_0"] clang_11_0 = ["clang_10_0"] +clang_12_0 = ["clang_11_0"] +clang_13_0 = ["clang_12_0"] clang_3_5 = [] clang_3_6 = ["clang_3_5"] clang_3_7 = ["clang_3_6"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index f7abf3c..a78a106 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -3,7 +3,7 @@ name = "clang-sys" authors = ["Kyle Mayes <kyle@mayeses.com>"] -version = "1.1.1" +version = "1.3.1" readme = "README.md" license = "Apache-2.0" @@ -31,6 +31,8 @@ clang_8_0 = ["clang_7_0"] clang_9_0 = ["clang_8_0"] clang_10_0 = ["clang_9_0"] clang_11_0 = ["clang_10_0"] +clang_12_0 = ["clang_11_0"] +clang_13_0 = ["clang_12_0"] runtime = ["libloading"] static = [] @@ -47,4 +49,4 @@ glob = "0.3" [package.metadata.docs.rs] -features = ["clang_10_0", "runtime"] +features = ["clang_13_0", "runtime"] @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/clang-sys/clang-sys-1.1.1.crate" + value: "https://static.crates.io/crates/clang-sys/clang-sys-1.3.1.crate" } - version: "1.1.1" + version: "1.3.1" license_type: NOTICE last_upgrade_date { - year: 2021 - month: 2 - day: 22 + year: 2022 + month: 3 + day: 1 } } @@ -2,76 +2,46 @@ [![Crate](https://img.shields.io/crates/v/clang-sys.svg)](https://crates.io/crates/clang-sys) [![Documentation](https://docs.rs/clang-sys/badge.svg)](https://docs.rs/clang-sys) -[![CI](https://github.com/KyleMayes/clang-sys/workflows/CI/badge.svg?branch=master)](https://github.com/KyleMayes/clang-sys/actions?query=workflow%3ACI) +[![CI](https://img.shields.io/github/workflow/status/KyleMayes/clang-sys/CI/master)](https://github.com/KyleMayes/vulkanalia/actions?query=workflow%3ACI) +![MSRV](https://img.shields.io/badge/MSRV-1.40.0-blue) Rust bindings for `libclang`. -If you are interested in a Rust wrapper for these bindings, see -[clang-rs](https://github.com/KyleMayes/clang-rs). - -Supported on the stable, beta, and nightly Rust channels.<br/> -Minimum supported Rust version: **1.40.0** +If you are interested in a somewhat idiomatic Rust wrapper for these bindings, see [`clang-rs`](https://github.com/KyleMayes/clang-rs). Released under the Apache License 2.0. -## Documentation +## [Documentation](https://docs.rs/clang-sys) -There are two versions of the documentation, one for the API exposed when -linking dynamically or statically and one for the API exposed when linking at -runtime (see the -[Dependencies](https://github.com/KyleMayes/clang-sys#dependencies) section -of the README for more information on the linking options). +Note that the documentation on https://docs.rs for this crate assumes usage of the `runtime` Cargo feature as well as the Cargo feature for the latest supported version of `libclang` (e.g., `clang_13_0`), neither of which are enabled by default. -The only difference between the APIs exposed is that when linking at runtime a -few additional types and functions are exposed to manage the loaded `libclang` -shared library. +Due to the usage of the `runtime` Cargo feature, this documentation will contain some additional types and functions to manage a dynamically loaded `libclang` instance at runtime. -* Runtime - [Documentation](https://kylemayes.github.io/clang-sys/runtime/clang_sys) -* Dynamic / Static - [Documentation](https://kylemayes.github.io/clang-sys/default/clang_sys) +Due to the usage of the Cargo feature for the latest supported version of `libclang`, this documentation will contain constants and functions that are not available in the oldest supported version of `libclang` (3.5). All of these types and functions have a documentation comment which specifies the minimum `libclang` version required to use the item. ## Supported Versions -To target a version of `libclang`, enable one of the following Cargo features: +To target a version of `libclang`, enable a Cargo features such as one of the following: * `clang_3_5` - requires `libclang` 3.5 or later * `clang_3_6` - requires `libclang` 3.6 or later -* `clang_3_7` - requires `libclang` 3.7 or later -* `clang_3_8` - requires `libclang` 3.8 or later -* `clang_3_9` - requires `libclang` 3.9 or later -* `clang_4_0` - requires `libclang` 4.0 or later -* `clang_5_0` - requires `libclang` 5.0 or later -* `clang_6_0` - requires `libclang` 6.0 or later -* `clang_7_0` - requires `libclang` 7.0 or later -* `clang_8_0` - requires `libclang` 8.0 or later -* `clang_9_0` - requires `libclang` 9.0 or later -* `clang_10_0` - requires `libclang` 10.0 or later -* `clang_11_0` - requires `libclang` 11.0 or later - -If you do not enable one of these features, the API provided by `libclang` 3.5 will be available by -default. +* etc... +* `clang_12_0` - requires `libclang` 12.0 or later +* `clang_13_0` - requires `libclang` 13.0 or later + +If you do not enable one of these features, the API provided by `libclang` 3.5 will be available by default. ## Dependencies -By default, this crate will attempt to link to `libclang` dynamically. In this case, this crate -depends on the `libclang` shared library (`libclang.so` on Linux, `libclang.dylib` on macOS, -`libclang.dll` on Windows). If you want to link to `libclang` statically instead, enable the -`static` Cargo feature. In this case, this crate depends on the LLVM and Clang static libraries. If -you don't want to link to `libclang` at compiletime but instead want to load it at runtime, enable -the `runtime` Cargo feature. +By default, this crate will attempt to link to `libclang` dynamically. In this case, this crate depends on the `libclang` shared library (`libclang.so` on Linux, `libclang.dylib` on macOS, `libclang.dll` on Windows). If you want to link to `libclang` statically instead, enable the `static` Cargo feature. In this case, this crate depends on the LLVM and Clang static libraries. If you don't want to link to `libclang` at compiletime but instead want to load it at runtime, enable the `runtime` Cargo feature. -These libraries can be either be installed as a part of Clang or downloaded -[here](http://llvm.org/releases/download.html). +These libraries can be either be installed as a part of Clang or downloaded [here](http://llvm.org/releases/download.html). -**Note:** The downloads for LLVM and Clang 3.8 and later do not include the `libclang.a` static -library. This means you cannot link to any of these versions of `libclang` statically unless you -build it from source. +**Note:** The downloads for LLVM and Clang 3.8 and later do not include the `libclang.a` static library. This means you cannot link to any of these versions of `libclang` statically unless you build it from source. ### Versioned Dependencies -This crate supports finding versioned instances of `libclang.so` (e.g.,`libclang-3.9.so`). -In the case where there are multiple instances to choose from, this crate will prefer instances with -higher versions. For example, the following instances of `libclang.so` are listed in descending -order of preference: +This crate supports finding versioned instances of `libclang.so` (e.g.,`libclang-3.9.so`). In the case where there are multiple instances to choose from, this crate will prefer instances with higher versions. For example, the following instances of `libclang.so` are listed in descending order of preference: 1. `libclang-4.0.so` 2. `libclang-4.so` @@ -79,23 +49,17 @@ order of preference: 4. `libclang-3.so` 5. `libclang.so` -**Note:** On BSD distributions, versioned instances of `libclang.so` matching the pattern -`libclang.so.*` (e.g., `libclang.so.7.0`) are also included. +**Note:** On BSD distributions, versioned instances of `libclang.so` matching the pattern `libclang.so.*` (e.g., `libclang.so.7.0`) are also included. -**Note:** On Linux distributions when the `runtime` features is enabled, versioned instances of -`libclang.so` matching the pattern `libclang.so.*` (e.g., `libclang.so.1`) are also included. +**Note:** On Linux distributions when the `runtime` features is enabled, versioned instances of `libclang.so` matching the pattern `libclang.so.*` (e.g., `libclang.so.1`) are also included. ## Environment Variables -The following environment variables, if set, are used by this crate to find the required libraries -and executables: +The following environment variables, if set, are used by this crate to find the required libraries and executables: -* `LLVM_CONFIG_PATH` **(compiletime)** - provides a full path to an `llvm-config` executable - (including the executable itself [i.e., `/usr/local/bin/llvm-config-8.0`]) -* `LIBCLANG_PATH` **(compiletime)** - provides a path to a directory containing a `libclang` shared - library or a full path to a specific `libclang` shared library -* `LIBCLANG_STATIC_PATH` **(compiletime)** - provides a path to a directory containing LLVM and - Clang static libraries +* `LLVM_CONFIG_PATH` **(compiletime)** - provides a full path to an `llvm-config` executable (including the executable itself [i.e., `/usr/local/bin/llvm-config-8.0`]) +* `LIBCLANG_PATH` **(compiletime)** - provides a path to a directory containing a `libclang` shared library or a full path to a specific `libclang` shared library +* `LIBCLANG_STATIC_PATH` **(compiletime)** - provides a path to a directory containing LLVM and Clang static libraries * `CLANG_PATH` **(runtime)** - provides a path to a `clang` executable ## Linking @@ -110,25 +74,14 @@ and executables: * a list of likely directories for the target platform (e.g., `/usr/local/lib` on Linux) * **macOS only:** the toolchain directory in the directory provided by `xcode-select --print-path` -On Linux, running an executable that has been dynamically linked to `libclang` may require you to -add a path to `libclang.so` to the `LD_LIBRARY_PATH` environment variable. The same is true on OS -X, except the `DYLD_LIBRARY_PATH` environment variable is used instead. +On Linux, running an executable that has been dynamically linked to `libclang` may require you to add a path to `libclang.so` to the `LD_LIBRARY_PATH` environment variable. The same is true on OS X, except the `DYLD_LIBRARY_PATH` environment variable is used instead. -On Windows, running an executable that has been dynamically linked to `libclang` requires that -`libclang.dll` can be found by the executable at runtime. See -[here](https://msdn.microsoft.com/en-us/library/7d83bc18.aspx) for more information. +On Windows, running an executable that has been dynamically linked to `libclang` requires that `libclang.dll` can be found by the executable at runtime. See [here](https://msdn.microsoft.com/en-us/library/7d83bc18.aspx) for more information. ### Static -The availability of `llvm-config` is not optional for static linking. Ensure that an instance of -this executable can be found on your system's path or set the `LLVM_CONFIG_PATH` environment -variable. The required LLVM and Clang static libraries will be searched for in the same way as -shared libraries are searched for, except the `LIBCLANG_STATIC_PATH` environment variable is used in -place of the `LIBCLANG_PATH` environment variable. +The availability of `llvm-config` is not optional for static linking. Ensure that an instance of this executable can be found on your system's path or set the `LLVM_CONFIG_PATH` environment variable. The required LLVM and Clang static libraries will be searched for in the same way as shared libraries are searched for, except the `LIBCLANG_STATIC_PATH` environment variable is used in place of the `LIBCLANG_PATH` environment variable. ### Runtime -The `clang_sys::load` function is used to load a `libclang` shared library for use in the thread in -which it is called. The `clang_sys::unload` function will unload the `libclang` shared library. -`clang_sys::load` searches for a `libclang` shared library in the same way one is searched for when -linking to `libclang` dynamically at compiletime. +The `clang_sys::load` function is used to load a `libclang` shared library for use in the thread in which it is called. The `clang_sys::unload` function will unload the `libclang` shared library. `clang_sys::load` searches for a `libclang` shared library in the same way one is searched for when linking to `libclang` dynamically at compiletime. diff --git a/TEST_MAPPING b/TEST_MAPPING index 7de080a..e4ec3b3 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,11 +1,24 @@ // Generated by update_crate_tests.py for tests that depend on this crate. { + "imports": [ + { + "path": "external/rust/crates/libsqlite3-sys" + } + ], "presubmit": [ { - "name": "libsqlite3-sys_device_test_src_lib" + "name": "keystore2_test" }, { + "name": "legacykeystore_test" + } + ], + "presubmit-rust": [ + { "name": "keystore2_test" + }, + { + "name": "legacykeystore_test" } ] } @@ -1,23 +1,11 @@ -// Copyright 2016 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 -//! Finds `libclang` static or dynamic libraries and links to them. +//! Finds `libclang` static or shared libraries and links to them. //! //! # Environment Variables //! //! This build script can make use of several environment variables to help it -//! find the required static or dynamic libraries. +//! find the required static or shared libraries. //! //! * `LLVM_CONFIG_PATH` - provides a path to an `llvm-config` executable //! * `LIBCLANG_PATH` - provides a path to a directory containing a `libclang` @@ -36,9 +24,9 @@ pub mod common; #[path = "build/dynamic.rs"] pub mod dynamic; #[path = "build/static.rs"] -pub mod static_; +pub mod r#static; -/// Copy the file from the supplied source to the supplied destination. +/// Copies a file. #[cfg(feature = "runtime")] fn copy(source: &str, destination: &Path) { use std::fs::File; @@ -55,7 +43,8 @@ fn copy(source: &str, destination: &Path) { .unwrap(); } -/// Generates the finding and linking code so that it may be used at runtime. +/// Copies the code used to find and link to `libclang` shared libraries into +/// the build output directory so that it may be used when linking at runtime. #[cfg(feature = "runtime")] fn main() { use std::env; @@ -69,11 +58,11 @@ fn main() { copy("build/dynamic.rs", &Path::new(&out).join("dynamic.rs")); } -/// Finds and links to the required libraries. +/// Finds and links to the required libraries dynamically or statically. #[cfg(not(feature = "runtime"))] fn main() { if cfg!(feature = "static") { - static_::link(); + r#static::link(); } else { dynamic::link(); } diff --git a/build/common.rs b/build/common.rs index 265a0cf..bc720ca 100644 --- a/build/common.rs +++ b/build/common.rs @@ -1,16 +1,4 @@ -// Copyright 2018 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 extern crate glob; @@ -20,87 +8,43 @@ use std::env; use std::path::{Path, PathBuf}; use std::process::Command; -use glob::MatchOptions; +use glob::{MatchOptions, Pattern}; -/// `libclang` directory patterns for FreeBSD and Linux. -const DIRECTORIES_LINUX: &[&str] = &[ - "/usr/lib*", - "/usr/lib*/*", - "/usr/lib*/*/*", - "/usr/local/lib*", - "/usr/local/lib*/*", - "/usr/local/lib*/*/*", - "/usr/local/llvm*/lib*", -]; - -/// `libclang` directory patterns for macOS. -const DIRECTORIES_MACOS: &[&str] = &[ - "/usr/local/opt/llvm*/lib", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", - "/Library/Developer/CommandLineTools/usr/lib", - "/usr/local/opt/llvm*/lib/llvm*/lib", -]; - -/// `libclang` directory patterns for Windows. -const DIRECTORIES_WINDOWS: &[&str] = &[ - "C:\\LLVM\\lib", - "C:\\Program Files*\\LLVM\\lib", - "C:\\MSYS*\\MinGW*\\lib", - // LLVM + Clang can be installed as a component of Visual Studio. - // https://github.com/KyleMayes/clang-sys/issues/121 - "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", -]; +//================================================ +// Commands +//================================================ thread_local! { - /// The errors encountered when attempting to execute console commands. + /// The errors encountered by the build script while executing commands. static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default(); } -/// Executes the supplied console command, returning the `stdout` output if the -/// command was successfully executed. -fn run_command(name: &str, command: &str, arguments: &[&str]) -> Option<String> { - macro_rules! error { - ($error:expr) => {{ - COMMAND_ERRORS.with(|e| e.borrow_mut() - .entry(name.into()) - .or_insert_with(Vec::new) - .push(format!( - "couldn't execute `{} {}` ({})", - command, - arguments.join(" "), - $error, - ))); - }}; - } - - let output = match Command::new(command).args(arguments).output() { - Ok(output) => output, - Err(error) => { - error!(format!("error: {}", error)); - return None; - } - }; - - if !output.status.success() { - error!(format!("exit code: {}", output.status)); - return None; - } - - Some(String::from_utf8_lossy(&output.stdout).into_owned()) -} - -/// Executes `llvm-config`, returning the `stdout` output if the command was -/// successfully executed. -pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { - let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); - run_command("llvm-config", &path, arguments) +/// Adds an error encountered by the build script while executing a command. +fn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) { + COMMAND_ERRORS.with(|e| { + e.borrow_mut() + .entry(name.into()) + .or_insert_with(Vec::new) + .push(format!( + "couldn't execute `{} {}` (path={}) ({})", + name, + arguments.join(" "), + path, + message, + )) + }); } -/// A struct that prints errors encountered when attempting to execute console -/// commands on drop if not discarded. +/// A struct that prints the errors encountered by the build script while +/// executing commands when dropped (unless explictly discarded). +/// +/// This is handy because we only want to print these errors when the build +/// script fails to link to an instance of `libclang`. For example, if +/// `llvm-config` couldn't be executed but an instance of `libclang` was found +/// anyway we don't want to pollute the build output with irrelevant errors. #[derive(Default)] pub struct CommandErrorPrinter { - discard: bool + discard: bool, } impl CommandErrorPrinter { @@ -123,7 +67,11 @@ impl Drop for CommandErrorPrinter { times, if the LLVM_CONFIG_PATH environment variable is set to \ a full path to valid `llvm-config` executable it will be used \ to try to find an instance of `libclang` on your system: {}", - errors.iter().map(|e| format!("\"{}\"", e)).collect::<Vec<_>>().join("\n "), + errors + .iter() + .map(|e| format!("\"{}\"", e)) + .collect::<Vec<_>>() + .join("\n "), ) } @@ -133,34 +81,127 @@ impl Drop for CommandErrorPrinter { times, if a valid instance of this executable is on your PATH \ it will be used to try to find an instance of `libclang` on \ your system: {}", - errors.iter().map(|e| format!("\"{}\"", e)).collect::<Vec<_>>().join("\n "), + errors + .iter() + .map(|e| format!("\"{}\"", e)) + .collect::<Vec<_>>() + .join("\n "), ) } } } -/// Returns the paths to and the filenames of the files matching the supplied -/// filename patterns in the supplied directory. +/// Executes a command and returns the `stdout` output if the command was +/// successfully executed (errors are added to `COMMAND_ERRORS`). +fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> { + let output = match Command::new(path).args(arguments).output() { + Ok(output) => output, + Err(error) => { + let message = format!("error: {}", error); + add_command_error(name, path, arguments, message); + return None; + } + }; + + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).into_owned()) + } else { + let message = format!("exit code: {}", output.status); + add_command_error(name, path, arguments, message); + None + } +} + +/// Executes the `llvm-config` command and returns the `stdout` output if the +/// command was successfully executed (errors are added to `COMMAND_ERRORS`). +pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { + let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); + run_command("llvm-config", &path, arguments) +} + +/// Executes the `xcode-select` command and returns the `stdout` output if the +/// command was successfully executed (errors are added to `COMMAND_ERRORS`). +pub fn run_xcode_select(arguments: &[&str]) -> Option<String> { + run_command("xcode-select", "xcode-select", arguments) +} + +//================================================ +// Search Directories +//================================================ + +/// `libclang` directory patterns for Haiku. +const DIRECTORIES_HAIKU: &[&str] = &[ + "/boot/system/lib", + "/boot/system/develop/lib", + "/boot/system/non-packaged/lib", + "/boot/system/non-packaged/develop/lib", + "/boot/home/config/non-packaged/lib", + "/boot/home/config/non-packaged/develop/lib", +]; + +/// `libclang` directory patterns for Linux (and FreeBSD). +const DIRECTORIES_LINUX: &[&str] = &[ + "/usr/lib*", + "/usr/lib*/*", + "/usr/lib*/*/*", + "/usr/local/lib*", + "/usr/local/lib*/*", + "/usr/local/lib*/*/*", + "/usr/local/llvm*/lib*", +]; + +/// `libclang` directory patterns for macOS. +const DIRECTORIES_MACOS: &[&str] = &[ + "/usr/local/opt/llvm*/lib", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", + "/Library/Developer/CommandLineTools/usr/lib", + "/usr/local/opt/llvm*/lib/llvm*/lib", +]; + +/// `libclang` directory patterns for Windows. +const DIRECTORIES_WINDOWS: &[&str] = &[ + "C:\\LLVM\\lib", + "C:\\Program Files*\\LLVM\\lib", + "C:\\MSYS*\\MinGW*\\lib", + // LLVM + Clang can be installed as a component of Visual Studio. + // https://github.com/KyleMayes/clang-sys/issues/121 + "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", + // LLVM + Clang can be installed using Scoop (https://scoop.sh). + // Other Windows package managers install LLVM + Clang to previously listed + // system-wide directories. + "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin", +]; + +//================================================ +// Searching +//================================================ + +/// Finds the files in a directory that match one or more filename glob patterns +/// and returns the paths to and filenames of those files. fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { - // Join the directory to the filename patterns to obtain the path patterns. + // Escape the specified directory in case it contains characters that have + // special meaning in glob patterns (e.g., `[` or `]`). + let directory = Pattern::escape(directory.to_str().unwrap()); + let directory = Path::new(&directory); + + // Join the escaped directory to the filename glob patterns to obtain + // complete glob patterns for the files being searched for. let paths = filenames .iter() - .filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned)); + .map(|f| directory.join(f).to_str().unwrap().to_owned()); - // Prevent wildcards from matching path separators. + // Prevent wildcards from matching path separators to ensure that the search + // is limited to the specified directory. let mut options = MatchOptions::new(); options.require_literal_separator = true; paths - .flat_map(|p| { - if let Ok(paths) = glob::glob_with(&p, options) { - paths.filter_map(Result::ok).collect() - } else { - vec![] - } - }) + .map(|p| glob::glob_with(&p, options)) + .filter_map(Result::ok) + .flatten() .filter_map(|p| { - let filename = p.file_name().and_then(|f| f.to_str())?; + let path = p.ok()?; + let filename = path.file_name()?.to_str().unwrap(); // The `libclang_shared` library has been renamed to `libclang-cpp` // in Clang 10. This can cause instances of this library (e.g., @@ -175,9 +216,9 @@ fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, Str .collect::<Vec<_>>() } -/// Returns the paths to and the filenames of the files matching the supplied -/// filename patterns in the supplied directory, checking any relevant sibling -/// directories. +/// Finds the files in a directory (and any relevant sibling directories) that +/// match one or more filename glob patterns and returns the paths to and +/// filenames of those files. fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { let mut results = search_directory(directory, filenames); @@ -194,54 +235,57 @@ fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, S results } -/// Returns the paths to and the filenames of the `libclang` static or dynamic -/// libraries matching the supplied filename patterns. -pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)> { - // Use the path provided by the relevant environment variable. +/// Finds the `libclang` static or dynamic libraries matching one or more +/// filename glob patterns and returns the paths to and filenames of those files. +pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> { + // Search only the path indicated by the relevant environment variable + // (e.g., `LIBCLANG_PATH`) if it is set. if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { - // Check if the path is referring to a matching file already. + // Check if the path is a matching file. if let Some(parent) = path.parent() { let filename = path.file_name().unwrap().to_str().unwrap(); - let libraries = search_directories(parent, files); + let libraries = search_directories(parent, filenames); if libraries.iter().any(|(_, f)| f == filename) { return vec![(parent.into(), filename.into())]; } } - return search_directories(&path, files); + // Check if the path is directory containing a matching file. + return search_directories(&path, filenames); } let mut found = vec![]; - // Search the `bin` and `lib` directories in directory provided by + // Search the `bin` and `lib` directories in the directory returned by // `llvm-config --prefix`. if let Some(output) = run_llvm_config(&["--prefix"]) { let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); - found.extend(search_directories(&directory.join("bin"), files)); - found.extend(search_directories(&directory.join("lib"), files)); - found.extend(search_directories(&directory.join("lib64"), files)); + found.extend(search_directories(&directory.join("bin"), filenames)); + found.extend(search_directories(&directory.join("lib"), filenames)); + found.extend(search_directories(&directory.join("lib64"), filenames)); } - // Search the toolchain directory in the directory provided by + // Search the toolchain directory in the directory returned by // `xcode-select --print-path`. if cfg!(target_os = "macos") { - if let Some(output) = run_command("xcode-select", "xcode-select", &["--print-path"]) { + if let Some(output) = run_xcode_select(&["--print-path"]) { let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); - found.extend(search_directories(&directory, files)); + found.extend(search_directories(&directory, filenames)); } } - // Search the directories provided by the `LD_LIBRARY_PATH` environment - // variable. + // Search the directories in the `LD_LIBRARY_PATH` environment variable. if let Ok(path) = env::var("LD_LIBRARY_PATH") { - for directory in path.split(':').map(Path::new) { - found.extend(search_directories(&directory, files)); + for directory in env::split_paths(&path) { + found.extend(search_directories(&directory, filenames)); } } // Determine the `libclang` directory patterns. - let directories = if cfg!(any(target_os = "freebsd", target_os = "linux")) { + let directories = if cfg!(target_os = "haiku") { + DIRECTORIES_HAIKU + } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { DIRECTORIES_LINUX } else if cfg!(target_os = "macos") { DIRECTORIES_MACOS @@ -258,7 +302,7 @@ pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(Pat for directory in directories.iter().rev() { if let Ok(directories) = glob::glob_with(directory, options) { for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { - found.extend(search_directories(&directory, files)); + found.extend(search_directories(&directory, filenames)); } } } diff --git a/build/dynamic.rs b/build/dynamic.rs index c15973c..39247c8 100644 --- a/build/dynamic.rs +++ b/build/dynamic.rs @@ -1,16 +1,4 @@ -// Copyright 2018 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 use std::env; use std::fs::File; @@ -19,7 +7,11 @@ use std::path::{Path, PathBuf}; use super::common; -/// Returns the ELF class from the ELF header in the supplied file. +//================================================ +// Validation +//================================================ + +/// Extracts the ELF class from the ELF header in a shared library. fn parse_elf_header(path: &Path) -> io::Result<u8> { let mut file = File::open(path)?; let mut buffer = [0; 5]; @@ -31,34 +23,34 @@ fn parse_elf_header(path: &Path) -> io::Result<u8> { } } -/// Returns the magic number from the PE header in the supplied file. +/// Extracts the magic number from the PE header in a shared library. fn parse_pe_header(path: &Path) -> io::Result<u16> { let mut file = File::open(path)?; - // Determine the header offset. + // Extract the header offset. let mut buffer = [0; 4]; let start = SeekFrom::Start(0x3C); file.seek(start)?; file.read_exact(&mut buffer)?; let offset = i32::from_le_bytes(buffer); - // Determine the validity of the header. + // Check the validity of the header. file.seek(SeekFrom::Start(offset as u64))?; file.read_exact(&mut buffer)?; if buffer != [80, 69, 0, 0] { return Err(Error::new(ErrorKind::InvalidData, "invalid PE header")); } - // Find the magic number. + // Extract the magic number. let mut buffer = [0; 2]; file.seek(SeekFrom::Current(20))?; file.read_exact(&mut buffer)?; Ok(u16::from_le_bytes(buffer)) } -/// Validates the header for the supplied `libclang` shared library. -fn validate_header(path: &Path) -> Result<(), String> { - if cfg!(any(target_os = "freebsd", target_os = "linux")) { +/// Checks that a `libclang` shared library matches the target platform. +fn validate_library(path: &Path) -> Result<(), String> { + if cfg!(any(target_os = "linux", target_os = "freebsd")) { let class = parse_elf_header(path).map_err(|e| e.to_string())?; if cfg!(target_pointer_width = "32") && class != 1 { @@ -87,11 +79,14 @@ fn validate_header(path: &Path) -> Result<(), String> { } } -/// Returns the components of the version in the supplied `libclang` shared -// library filename. +//================================================ +// Searching +//================================================ + +/// Extracts the version components in a `libclang` shared library filename. fn parse_version(filename: &str) -> Vec<u32> { - let version = if filename.starts_with("libclang.so.") { - &filename[12..] + let version = if let Some(version) = filename.strip_prefix("libclang.so.") { + version } else if filename.starts_with("libclang-") { &filename[9..filename.len() - 3] } else { @@ -101,8 +96,8 @@ fn parse_version(filename: &str) -> Vec<u32> { version.split('.').map(|s| s.parse().unwrap_or(0)).collect() } -/// Returns the paths to, the filenames, and the versions of the `libclang` -// shared libraries. +/// Finds `libclang` shared libraries and returns the paths to, filenames of, +/// and versions of those shared libraries. fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Vec<u32>)>, String> { let mut files = vec![format!( "{}clang{}", @@ -127,9 +122,10 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve } if cfg!(any( - target_os = "openbsd", target_os = "freebsd", - target_os = "netbsd" + target_os = "haiku", + target_os = "netbsd", + target_os = "openbsd", )) { // Some BSD distributions don't create a `libclang.so` symlink either, // but use a different naming scheme for versioned files (e.g., @@ -143,12 +139,12 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve files.push("libclang.dll".into()); } - // Validate the `libclang` shared libraries and collect the versions. + // Find and validate `libclang` shared libraries and collect the versions. let mut valid = vec![]; let mut invalid = vec![]; for (directory, filename) in common::search_libclang_directories(&files, "LIBCLANG_PATH") { let path = directory.join(&filename); - match validate_header(&path) { + match validate_library(&path) { Ok(()) => { let version = parse_version(&filename); valid.push((directory, filename, version)) @@ -176,8 +172,8 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve Err(message) } -/// Returns the directory and filename of the "best" available `libclang` shared -/// library. +/// Finds the "best" `libclang` shared library and returns the directory and +/// filename of that library. pub fn find(runtime: bool) -> Result<(PathBuf, String), String> { search_libclang_directories(runtime)? .iter() @@ -201,7 +197,11 @@ pub fn find(runtime: bool) -> Result<(PathBuf, String), String> { .ok_or_else(|| "unreachable".into()) } -/// Find and link to `libclang` dynamically. +//================================================ +// Linking +//================================================ + +/// Finds and links to a `libclang` shared library. #[cfg(not(feature = "runtime"))] pub fn link() { let cep = common::CommandErrorPrinter::default(); @@ -252,7 +252,7 @@ pub fn link() { // `libclang.so.7.0`). let name = match name.find(".dylib").or_else(|| name.find(".so")) { Some(index) => &name[0..index], - None => &name, + None => name, }; println!("cargo:rustc-link-lib=dylib={}", name); diff --git a/build/static.rs b/build/static.rs index 83a8185..6af914f 100644 --- a/build/static.rs +++ b/build/static.rs @@ -1,36 +1,47 @@ -// Copyright 2018 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 extern crate glob; use std::path::{Path, PathBuf}; +use glob::Pattern; + use common; -/// Returns the name of an LLVM or Clang library from a path to such a library. +//================================================ +// Searching +//================================================ + +/// Clang static libraries required to link to `libclang` 3.5 and later. +const CLANG_LIBRARIES: &[&str] = &[ + "clang", + "clangAST", + "clangAnalysis", + "clangBasic", + "clangDriver", + "clangEdit", + "clangFrontend", + "clangIndex", + "clangLex", + "clangParse", + "clangRewrite", + "clangSema", + "clangSerialization", +]; + +/// Gets the name of an LLVM or Clang static library from a path. fn get_library_name(path: &Path) -> Option<String> { path.file_stem().map(|p| { let string = p.to_string_lossy(); - if string.starts_with("lib") { - string[3..].to_owned() + if let Some(name) = string.strip_prefix("lib") { + name.to_owned() } else { string.to_string() } }) } -/// Returns the LLVM libraries required to link to `libclang` statically. +/// Gets the LLVM static libraries required to link to `libclang`. fn get_llvm_libraries() -> Vec<String> { common::run_llvm_config(&["--libs"]) .unwrap() @@ -39,8 +50,8 @@ fn get_llvm_libraries() -> Vec<String> { // Depending on the version of `llvm-config` in use, listed // libraries may be in one of two forms, a full path to the library // or simply prefixed with `-l`. - if p.starts_with("-l") { - Some(p[2..].into()) + if let Some(path) = p.strip_prefix("-l") { + Some(path.into()) } else { get_library_name(Path::new(p)) } @@ -48,30 +59,14 @@ fn get_llvm_libraries() -> Vec<String> { .collect() } -/// Clang libraries required to link to `libclang` 3.5 and later statically. -const CLANG_LIBRARIES: &[&str] = &[ - "clang", - "clangAST", - "clangAnalysis", - "clangBasic", - "clangDriver", - "clangEdit", - "clangFrontend", - "clangIndex", - "clangLex", - "clangParse", - "clangRewrite", - "clangSema", - "clangSerialization", -]; - -/// Returns the Clang libraries required to link to `libclang` statically. +/// Gets the Clang static libraries required to link to `libclang`. fn get_clang_libraries<P: AsRef<Path>>(directory: P) -> Vec<String> { - let pattern = directory - .as_ref() - .join("libclang*.a") - .to_string_lossy() - .to_string(); + // Escape the directory in case it contains characters that have special + // meaning in glob patterns (e.g., `[` or `]`). + let directory = Pattern::escape(directory.as_ref().to_str().unwrap()); + let directory = Path::new(&directory); + + let pattern = directory.join("libclang*.a").to_str().unwrap().to_owned(); if let Ok(libraries) = glob::glob(&pattern) { libraries .filter_map(|l| l.ok().and_then(|l| get_library_name(&l))) @@ -81,7 +76,8 @@ fn get_clang_libraries<P: AsRef<Path>>(directory: P) -> Vec<String> { } } -/// Returns a directory containing `libclang` static libraries. +/// Finds a directory containing LLVM and Clang static libraries and returns the +/// path to that directory. fn find() -> PathBuf { let name = if cfg!(target_os = "windows") { "libclang.lib" @@ -97,7 +93,11 @@ fn find() -> PathBuf { } } -/// Find and link to `libclang` statically. +//================================================ +// Linking +//================================================ + +/// Finds and links to `libclang` static libraries. pub fn link() { let cep = common::CommandErrorPrinter::default(); @@ -130,7 +130,7 @@ pub fn link() { // MSVC doesn't need this, as it tracks dependencies inside `.lib` files. if cfg!(target_os = "freebsd") { println!("cargo:rustc-flags=-l ffi -l ncursesw -l c++ -l z"); - } else if cfg!(target_os = "linux") { + } else if cfg!(any(target_os = "haiku", target_os = "linux")) { println!("cargo:rustc-flags=-l ffi -l ncursesw -l stdc++ -l z"); } else if cfg!(target_os = "macos") { println!("cargo:rustc-flags=-l ffi -l ncurses -l c++ -l z"); diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..a0f1a8e --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,5 @@ +{ + "copy-out": true, + "features": "runtime,clang_10_0", + "run": true +}
\ No newline at end of file diff --git a/out/common.rs b/out/common.rs index 265a0cf..bc720ca 100644 --- a/out/common.rs +++ b/out/common.rs @@ -1,16 +1,4 @@ -// Copyright 2018 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 extern crate glob; @@ -20,87 +8,43 @@ use std::env; use std::path::{Path, PathBuf}; use std::process::Command; -use glob::MatchOptions; +use glob::{MatchOptions, Pattern}; -/// `libclang` directory patterns for FreeBSD and Linux. -const DIRECTORIES_LINUX: &[&str] = &[ - "/usr/lib*", - "/usr/lib*/*", - "/usr/lib*/*/*", - "/usr/local/lib*", - "/usr/local/lib*/*", - "/usr/local/lib*/*/*", - "/usr/local/llvm*/lib*", -]; - -/// `libclang` directory patterns for macOS. -const DIRECTORIES_MACOS: &[&str] = &[ - "/usr/local/opt/llvm*/lib", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", - "/Library/Developer/CommandLineTools/usr/lib", - "/usr/local/opt/llvm*/lib/llvm*/lib", -]; - -/// `libclang` directory patterns for Windows. -const DIRECTORIES_WINDOWS: &[&str] = &[ - "C:\\LLVM\\lib", - "C:\\Program Files*\\LLVM\\lib", - "C:\\MSYS*\\MinGW*\\lib", - // LLVM + Clang can be installed as a component of Visual Studio. - // https://github.com/KyleMayes/clang-sys/issues/121 - "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", -]; +//================================================ +// Commands +//================================================ thread_local! { - /// The errors encountered when attempting to execute console commands. + /// The errors encountered by the build script while executing commands. static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default(); } -/// Executes the supplied console command, returning the `stdout` output if the -/// command was successfully executed. -fn run_command(name: &str, command: &str, arguments: &[&str]) -> Option<String> { - macro_rules! error { - ($error:expr) => {{ - COMMAND_ERRORS.with(|e| e.borrow_mut() - .entry(name.into()) - .or_insert_with(Vec::new) - .push(format!( - "couldn't execute `{} {}` ({})", - command, - arguments.join(" "), - $error, - ))); - }}; - } - - let output = match Command::new(command).args(arguments).output() { - Ok(output) => output, - Err(error) => { - error!(format!("error: {}", error)); - return None; - } - }; - - if !output.status.success() { - error!(format!("exit code: {}", output.status)); - return None; - } - - Some(String::from_utf8_lossy(&output.stdout).into_owned()) -} - -/// Executes `llvm-config`, returning the `stdout` output if the command was -/// successfully executed. -pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { - let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); - run_command("llvm-config", &path, arguments) +/// Adds an error encountered by the build script while executing a command. +fn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) { + COMMAND_ERRORS.with(|e| { + e.borrow_mut() + .entry(name.into()) + .or_insert_with(Vec::new) + .push(format!( + "couldn't execute `{} {}` (path={}) ({})", + name, + arguments.join(" "), + path, + message, + )) + }); } -/// A struct that prints errors encountered when attempting to execute console -/// commands on drop if not discarded. +/// A struct that prints the errors encountered by the build script while +/// executing commands when dropped (unless explictly discarded). +/// +/// This is handy because we only want to print these errors when the build +/// script fails to link to an instance of `libclang`. For example, if +/// `llvm-config` couldn't be executed but an instance of `libclang` was found +/// anyway we don't want to pollute the build output with irrelevant errors. #[derive(Default)] pub struct CommandErrorPrinter { - discard: bool + discard: bool, } impl CommandErrorPrinter { @@ -123,7 +67,11 @@ impl Drop for CommandErrorPrinter { times, if the LLVM_CONFIG_PATH environment variable is set to \ a full path to valid `llvm-config` executable it will be used \ to try to find an instance of `libclang` on your system: {}", - errors.iter().map(|e| format!("\"{}\"", e)).collect::<Vec<_>>().join("\n "), + errors + .iter() + .map(|e| format!("\"{}\"", e)) + .collect::<Vec<_>>() + .join("\n "), ) } @@ -133,34 +81,127 @@ impl Drop for CommandErrorPrinter { times, if a valid instance of this executable is on your PATH \ it will be used to try to find an instance of `libclang` on \ your system: {}", - errors.iter().map(|e| format!("\"{}\"", e)).collect::<Vec<_>>().join("\n "), + errors + .iter() + .map(|e| format!("\"{}\"", e)) + .collect::<Vec<_>>() + .join("\n "), ) } } } -/// Returns the paths to and the filenames of the files matching the supplied -/// filename patterns in the supplied directory. +/// Executes a command and returns the `stdout` output if the command was +/// successfully executed (errors are added to `COMMAND_ERRORS`). +fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> { + let output = match Command::new(path).args(arguments).output() { + Ok(output) => output, + Err(error) => { + let message = format!("error: {}", error); + add_command_error(name, path, arguments, message); + return None; + } + }; + + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).into_owned()) + } else { + let message = format!("exit code: {}", output.status); + add_command_error(name, path, arguments, message); + None + } +} + +/// Executes the `llvm-config` command and returns the `stdout` output if the +/// command was successfully executed (errors are added to `COMMAND_ERRORS`). +pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { + let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); + run_command("llvm-config", &path, arguments) +} + +/// Executes the `xcode-select` command and returns the `stdout` output if the +/// command was successfully executed (errors are added to `COMMAND_ERRORS`). +pub fn run_xcode_select(arguments: &[&str]) -> Option<String> { + run_command("xcode-select", "xcode-select", arguments) +} + +//================================================ +// Search Directories +//================================================ + +/// `libclang` directory patterns for Haiku. +const DIRECTORIES_HAIKU: &[&str] = &[ + "/boot/system/lib", + "/boot/system/develop/lib", + "/boot/system/non-packaged/lib", + "/boot/system/non-packaged/develop/lib", + "/boot/home/config/non-packaged/lib", + "/boot/home/config/non-packaged/develop/lib", +]; + +/// `libclang` directory patterns for Linux (and FreeBSD). +const DIRECTORIES_LINUX: &[&str] = &[ + "/usr/lib*", + "/usr/lib*/*", + "/usr/lib*/*/*", + "/usr/local/lib*", + "/usr/local/lib*/*", + "/usr/local/lib*/*/*", + "/usr/local/llvm*/lib*", +]; + +/// `libclang` directory patterns for macOS. +const DIRECTORIES_MACOS: &[&str] = &[ + "/usr/local/opt/llvm*/lib", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", + "/Library/Developer/CommandLineTools/usr/lib", + "/usr/local/opt/llvm*/lib/llvm*/lib", +]; + +/// `libclang` directory patterns for Windows. +const DIRECTORIES_WINDOWS: &[&str] = &[ + "C:\\LLVM\\lib", + "C:\\Program Files*\\LLVM\\lib", + "C:\\MSYS*\\MinGW*\\lib", + // LLVM + Clang can be installed as a component of Visual Studio. + // https://github.com/KyleMayes/clang-sys/issues/121 + "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", + // LLVM + Clang can be installed using Scoop (https://scoop.sh). + // Other Windows package managers install LLVM + Clang to previously listed + // system-wide directories. + "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin", +]; + +//================================================ +// Searching +//================================================ + +/// Finds the files in a directory that match one or more filename glob patterns +/// and returns the paths to and filenames of those files. fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { - // Join the directory to the filename patterns to obtain the path patterns. + // Escape the specified directory in case it contains characters that have + // special meaning in glob patterns (e.g., `[` or `]`). + let directory = Pattern::escape(directory.to_str().unwrap()); + let directory = Path::new(&directory); + + // Join the escaped directory to the filename glob patterns to obtain + // complete glob patterns for the files being searched for. let paths = filenames .iter() - .filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned)); + .map(|f| directory.join(f).to_str().unwrap().to_owned()); - // Prevent wildcards from matching path separators. + // Prevent wildcards from matching path separators to ensure that the search + // is limited to the specified directory. let mut options = MatchOptions::new(); options.require_literal_separator = true; paths - .flat_map(|p| { - if let Ok(paths) = glob::glob_with(&p, options) { - paths.filter_map(Result::ok).collect() - } else { - vec![] - } - }) + .map(|p| glob::glob_with(&p, options)) + .filter_map(Result::ok) + .flatten() .filter_map(|p| { - let filename = p.file_name().and_then(|f| f.to_str())?; + let path = p.ok()?; + let filename = path.file_name()?.to_str().unwrap(); // The `libclang_shared` library has been renamed to `libclang-cpp` // in Clang 10. This can cause instances of this library (e.g., @@ -175,9 +216,9 @@ fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, Str .collect::<Vec<_>>() } -/// Returns the paths to and the filenames of the files matching the supplied -/// filename patterns in the supplied directory, checking any relevant sibling -/// directories. +/// Finds the files in a directory (and any relevant sibling directories) that +/// match one or more filename glob patterns and returns the paths to and +/// filenames of those files. fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { let mut results = search_directory(directory, filenames); @@ -194,54 +235,57 @@ fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, S results } -/// Returns the paths to and the filenames of the `libclang` static or dynamic -/// libraries matching the supplied filename patterns. -pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)> { - // Use the path provided by the relevant environment variable. +/// Finds the `libclang` static or dynamic libraries matching one or more +/// filename glob patterns and returns the paths to and filenames of those files. +pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> { + // Search only the path indicated by the relevant environment variable + // (e.g., `LIBCLANG_PATH`) if it is set. if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { - // Check if the path is referring to a matching file already. + // Check if the path is a matching file. if let Some(parent) = path.parent() { let filename = path.file_name().unwrap().to_str().unwrap(); - let libraries = search_directories(parent, files); + let libraries = search_directories(parent, filenames); if libraries.iter().any(|(_, f)| f == filename) { return vec![(parent.into(), filename.into())]; } } - return search_directories(&path, files); + // Check if the path is directory containing a matching file. + return search_directories(&path, filenames); } let mut found = vec![]; - // Search the `bin` and `lib` directories in directory provided by + // Search the `bin` and `lib` directories in the directory returned by // `llvm-config --prefix`. if let Some(output) = run_llvm_config(&["--prefix"]) { let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); - found.extend(search_directories(&directory.join("bin"), files)); - found.extend(search_directories(&directory.join("lib"), files)); - found.extend(search_directories(&directory.join("lib64"), files)); + found.extend(search_directories(&directory.join("bin"), filenames)); + found.extend(search_directories(&directory.join("lib"), filenames)); + found.extend(search_directories(&directory.join("lib64"), filenames)); } - // Search the toolchain directory in the directory provided by + // Search the toolchain directory in the directory returned by // `xcode-select --print-path`. if cfg!(target_os = "macos") { - if let Some(output) = run_command("xcode-select", "xcode-select", &["--print-path"]) { + if let Some(output) = run_xcode_select(&["--print-path"]) { let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); - found.extend(search_directories(&directory, files)); + found.extend(search_directories(&directory, filenames)); } } - // Search the directories provided by the `LD_LIBRARY_PATH` environment - // variable. + // Search the directories in the `LD_LIBRARY_PATH` environment variable. if let Ok(path) = env::var("LD_LIBRARY_PATH") { - for directory in path.split(':').map(Path::new) { - found.extend(search_directories(&directory, files)); + for directory in env::split_paths(&path) { + found.extend(search_directories(&directory, filenames)); } } // Determine the `libclang` directory patterns. - let directories = if cfg!(any(target_os = "freebsd", target_os = "linux")) { + let directories = if cfg!(target_os = "haiku") { + DIRECTORIES_HAIKU + } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { DIRECTORIES_LINUX } else if cfg!(target_os = "macos") { DIRECTORIES_MACOS @@ -258,7 +302,7 @@ pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(Pat for directory in directories.iter().rev() { if let Ok(directories) = glob::glob_with(directory, options) { for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { - found.extend(search_directories(&directory, files)); + found.extend(search_directories(&directory, filenames)); } } } diff --git a/out/dynamic.rs b/out/dynamic.rs index c15973c..39247c8 100644 --- a/out/dynamic.rs +++ b/out/dynamic.rs @@ -1,16 +1,4 @@ -// Copyright 2018 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 use std::env; use std::fs::File; @@ -19,7 +7,11 @@ use std::path::{Path, PathBuf}; use super::common; -/// Returns the ELF class from the ELF header in the supplied file. +//================================================ +// Validation +//================================================ + +/// Extracts the ELF class from the ELF header in a shared library. fn parse_elf_header(path: &Path) -> io::Result<u8> { let mut file = File::open(path)?; let mut buffer = [0; 5]; @@ -31,34 +23,34 @@ fn parse_elf_header(path: &Path) -> io::Result<u8> { } } -/// Returns the magic number from the PE header in the supplied file. +/// Extracts the magic number from the PE header in a shared library. fn parse_pe_header(path: &Path) -> io::Result<u16> { let mut file = File::open(path)?; - // Determine the header offset. + // Extract the header offset. let mut buffer = [0; 4]; let start = SeekFrom::Start(0x3C); file.seek(start)?; file.read_exact(&mut buffer)?; let offset = i32::from_le_bytes(buffer); - // Determine the validity of the header. + // Check the validity of the header. file.seek(SeekFrom::Start(offset as u64))?; file.read_exact(&mut buffer)?; if buffer != [80, 69, 0, 0] { return Err(Error::new(ErrorKind::InvalidData, "invalid PE header")); } - // Find the magic number. + // Extract the magic number. let mut buffer = [0; 2]; file.seek(SeekFrom::Current(20))?; file.read_exact(&mut buffer)?; Ok(u16::from_le_bytes(buffer)) } -/// Validates the header for the supplied `libclang` shared library. -fn validate_header(path: &Path) -> Result<(), String> { - if cfg!(any(target_os = "freebsd", target_os = "linux")) { +/// Checks that a `libclang` shared library matches the target platform. +fn validate_library(path: &Path) -> Result<(), String> { + if cfg!(any(target_os = "linux", target_os = "freebsd")) { let class = parse_elf_header(path).map_err(|e| e.to_string())?; if cfg!(target_pointer_width = "32") && class != 1 { @@ -87,11 +79,14 @@ fn validate_header(path: &Path) -> Result<(), String> { } } -/// Returns the components of the version in the supplied `libclang` shared -// library filename. +//================================================ +// Searching +//================================================ + +/// Extracts the version components in a `libclang` shared library filename. fn parse_version(filename: &str) -> Vec<u32> { - let version = if filename.starts_with("libclang.so.") { - &filename[12..] + let version = if let Some(version) = filename.strip_prefix("libclang.so.") { + version } else if filename.starts_with("libclang-") { &filename[9..filename.len() - 3] } else { @@ -101,8 +96,8 @@ fn parse_version(filename: &str) -> Vec<u32> { version.split('.').map(|s| s.parse().unwrap_or(0)).collect() } -/// Returns the paths to, the filenames, and the versions of the `libclang` -// shared libraries. +/// Finds `libclang` shared libraries and returns the paths to, filenames of, +/// and versions of those shared libraries. fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Vec<u32>)>, String> { let mut files = vec![format!( "{}clang{}", @@ -127,9 +122,10 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve } if cfg!(any( - target_os = "openbsd", target_os = "freebsd", - target_os = "netbsd" + target_os = "haiku", + target_os = "netbsd", + target_os = "openbsd", )) { // Some BSD distributions don't create a `libclang.so` symlink either, // but use a different naming scheme for versioned files (e.g., @@ -143,12 +139,12 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve files.push("libclang.dll".into()); } - // Validate the `libclang` shared libraries and collect the versions. + // Find and validate `libclang` shared libraries and collect the versions. let mut valid = vec![]; let mut invalid = vec![]; for (directory, filename) in common::search_libclang_directories(&files, "LIBCLANG_PATH") { let path = directory.join(&filename); - match validate_header(&path) { + match validate_library(&path) { Ok(()) => { let version = parse_version(&filename); valid.push((directory, filename, version)) @@ -176,8 +172,8 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve Err(message) } -/// Returns the directory and filename of the "best" available `libclang` shared -/// library. +/// Finds the "best" `libclang` shared library and returns the directory and +/// filename of that library. pub fn find(runtime: bool) -> Result<(PathBuf, String), String> { search_libclang_directories(runtime)? .iter() @@ -201,7 +197,11 @@ pub fn find(runtime: bool) -> Result<(PathBuf, String), String> { .ok_or_else(|| "unreachable".into()) } -/// Find and link to `libclang` dynamically. +//================================================ +// Linking +//================================================ + +/// Finds and links to a `libclang` shared library. #[cfg(not(feature = "runtime"))] pub fn link() { let cep = common::CommandErrorPrinter::default(); @@ -252,7 +252,7 @@ pub fn link() { // `libclang.so.7.0`). let name = match name.find(".dylib").or_else(|| name.find(".so")) { Some(index) => &name[0..index], - None => &name, + None => name, }; println!("cargo:rustc-link-lib=dylib={}", name); @@ -1,33 +1,23 @@ -// Copyright 2016 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 //! Rust bindings for `libclang`. //! -//! ## Documentation +//! ## [Documentation](https://docs.rs/clang-sys) //! -//! There are two versions of the documentation, one for the API exposed when -//! linking dynamically or statically and one for the API exposed when linking -//! at runtime (see the -//! [Dependencies](https://github.com/KyleMayes/clang-sys#dependencies) section -//! of the README for more information on the linking options). +//! Note that the documentation on https://docs.rs for this crate assumes usage +//! of the `runtime` Cargo feature as well as the Cargo feature for the latest +//! supported version of `libclang` (e.g., `clang_11_0`), neither of which are +//! enabled by default. //! -//! The only difference between the APIs exposed is that when linking at runtime -//! a few additional types and functions are exposed to manage the loaded -//! `libclang` shared library. +//! Due to the usage of the `runtime` Cargo feature, this documentation will +//! contain some additional types and functions to manage a dynamically loaded +//! `libclang` instance at runtime. //! -//! * Runtime - [Documentation](https://kylemayes.github.io/clang-sys/runtime/clang_sys) -//! * Dynamic / Static - [Documentation](https://kylemayes.github.io/clang-sys/default/clang_sys) +//! Due to the usage of the Cargo feature for the latest supported version of +//! `libclang`, this documentation will contain constants and functions that are +//! not available in the oldest supported version of `libclang` (3.5). All of +//! these types and functions have a documentation comment which specifies the +//! minimum `libclang` version required to use the item. #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] #![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal))] @@ -135,6 +125,8 @@ cenum! { const CXCallingConv_AArch64VectorCall = 16, const CXCallingConv_Invalid = 100, const CXCallingConv_Unexposed = 200, + /// Only produced by `libclang` 13.0 and later. + const CXCallingConv_SwiftAsync = 17, } } @@ -323,6 +315,12 @@ cenum! { const CXCursor_ObjCAvailabilityCheckExpr = 148, /// Only produced by `libclang` 7.0 and later. const CXCursor_FixedPointLiteral = 149, + /// Only produced by `libclang` 12.0 and later. + const CXCursor_OMPArrayShapingExpr = 150, + /// Only produced by `libclang` 12.0 and later. + const CXCursor_OMPIteratorExpr = 151, + /// Only produced by `libclang` 12.0 and later. + const CXCursor_CXXAddrspaceCastExpr = 152, const CXCursor_UnexposedStmt = 200, const CXCursor_LabelStmt = 201, const CXCursor_CompoundStmt = 202, @@ -452,6 +450,18 @@ cenum! { const CXCursor_OMPDepobjDirective = 286, /// Only produced by `libclang` 11.0 and later. const CXCursor_OMPScanDirective = 287, + /// Only produced by `libclang` 13.0 and later. + const CXCursor_OMPTileDirective = 288, + /// Only produced by `libclang` 13.0 and later. + const CXCursor_OMPCanonicalLoop = 289, + /// Only produced by `libclang` 13.0 and later. + const CXCursor_OMPInteropDirective = 290, + /// Only produced by `libclang` 13.0 and later. + const CXCursor_OMPDispatchDirective = 291, + /// Only produced by `libclang` 13.0 and later. + const CXCursor_OMPMaskedDirective = 292, + /// Only produced by `libclang` 13.0 and later. + const CXCursor_OMPUnrollDirective = 293, const CXCursor_TranslationUnit = 300, const CXCursor_UnexposedAttr = 400, const CXCursor_IBActionAttr = 401, @@ -1031,6 +1041,8 @@ cenum! { const CXTypeNullability_Nullable = 1, const CXTypeNullability_Unspecified = 2, const CXTypeNullability_Invalid = 3, + /// Only produced by `libclang` 12.0 and later. + const CXTypeNullability_NullableResult = 4, } } @@ -1762,12 +1774,6 @@ link! { /// Only available on `libclang` 3.7 and later. #[cfg(feature = "clang_3_7")] pub fn clang_Cursor_getOffsetOfField(cursor: CXCursor) -> c_longlong; - /// Only available on `libclang` 9.0 and later. - #[cfg(feature = "clang_9_0")] - pub fn clang_Cursor_isAnonymousRecordDecl(cursor: CXCursor) -> c_uint; - /// Only available on `libclang` 9.0 and later. - #[cfg(feature = "clang_9_0")] - pub fn clang_Cursor_isInlineNamespace(cursor: CXCursor) -> c_uint; pub fn clang_Cursor_getRawCommentText(cursor: CXCursor) -> CXString; pub fn clang_Cursor_getReceiverType(cursor: CXCursor) -> CXType; pub fn clang_Cursor_getSpellingNameRange(cursor: CXCursor, index: c_uint, reserved: c_uint) -> CXSourceRange; @@ -1787,12 +1793,24 @@ link! { #[cfg(feature = "clang_3_6")] pub fn clang_Cursor_getTemplateArgumentValue(cursor: CXCursor, index: c_uint) -> c_longlong; pub fn clang_Cursor_getTranslationUnit(cursor: CXCursor) -> CXTranslationUnit; + /// Only available on `libclang` 12.0 and later. + #[cfg(feature = "clang_12_0")] + pub fn clang_Cursor_getVarDeclInitializer(cursor: CXCursor) -> CXCursor; /// Only available on `libclang` 3.9 and later. #[cfg(feature = "clang_3_9")] pub fn clang_Cursor_hasAttrs(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 12.0 and later. + #[cfg(feature = "clang_12_0")] + pub fn clang_Cursor_hasVarDeclGlobalStorage(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 12.0 and later. + #[cfg(feature = "clang_12_0")] + pub fn clang_Cursor_hasVarDeclExternalStorage(cursor: CXCursor) -> c_uint; /// Only available on `libclang` 3.7 and later. #[cfg(feature = "clang_3_7")] pub fn clang_Cursor_isAnonymous(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 9.0 and later. + #[cfg(feature = "clang_9_0")] + pub fn clang_Cursor_isAnonymousRecordDecl(cursor: CXCursor) -> c_uint; pub fn clang_Cursor_isBitField(cursor: CXCursor) -> c_uint; pub fn clang_Cursor_isDynamicCall(cursor: CXCursor) -> c_int; /// Only available on `libclang` 5.0 and later. @@ -1801,6 +1819,9 @@ link! { /// Only available on `libclang` 3.9 and later. #[cfg(feature = "clang_3_9")] pub fn clang_Cursor_isFunctionInlined(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 9.0 and later. + #[cfg(feature = "clang_9_0")] + pub fn clang_Cursor_isInlineNamespace(cursor: CXCursor) -> c_uint; /// Only available on `libclang` 3.9 and later. #[cfg(feature = "clang_3_9")] pub fn clang_Cursor_isMacroBuiltin(cursor: CXCursor) -> c_uint; @@ -1876,35 +1897,35 @@ link! { pub fn clang_Type_getAlignOf(type_: CXType) -> c_longlong; pub fn clang_Type_getCXXRefQualifier(type_: CXType) -> CXRefQualifierKind; pub fn clang_Type_getClassType(type_: CXType) -> CXType; + /// Only available on `libclang` 8.0 and later. + #[cfg(feature = "clang_8_0")] + pub fn clang_Type_getModifiedType(type_: CXType) -> CXType; /// Only available on `libclang` 3.9 and later. #[cfg(feature = "clang_3_9")] pub fn clang_Type_getNamedType(type_: CXType) -> CXType; - pub fn clang_Type_getNumTemplateArguments(type_: CXType) -> c_int; /// Only available on `libclang` 8.0 and later. #[cfg(feature = "clang_8_0")] - pub fn clang_Type_getObjCObjectBaseType(type_: CXType) -> CXType; + pub fn clang_Type_getNullability(type_: CXType) -> CXTypeNullabilityKind; /// Only available on `libclang` 8.0 and later. #[cfg(feature = "clang_8_0")] pub fn clang_Type_getNumObjCProtocolRefs(type_: CXType) -> c_uint; /// Only available on `libclang` 8.0 and later. #[cfg(feature = "clang_8_0")] - pub fn clang_Type_getObjCProtocolDecl(type_: CXType, index: c_uint) -> CXCursor; - /// Only available on `libclang` 8.0 and later. - #[cfg(feature = "clang_8_0")] pub fn clang_Type_getNumObjCTypeArgs(type_: CXType) -> c_uint; - /// Only available on `libclang` 8.0 and later. - #[cfg(feature = "clang_8_0")] - pub fn clang_Type_getObjCTypeArg(type_: CXType, index: c_uint) -> CXType; + pub fn clang_Type_getNumTemplateArguments(type_: CXType) -> c_int; /// Only available on `libclang` 3.9 and later. #[cfg(feature = "clang_3_9")] pub fn clang_Type_getObjCEncoding(type_: CXType) -> CXString; - pub fn clang_Type_getOffsetOf(type_: CXType, field: *const c_char) -> c_longlong; /// Only available on `libclang` 8.0 and later. #[cfg(feature = "clang_8_0")] - pub fn clang_Type_getModifiedType(type_: CXType) -> CXType; + pub fn clang_Type_getObjCObjectBaseType(type_: CXType) -> CXType; /// Only available on `libclang` 8.0 and later. #[cfg(feature = "clang_8_0")] - pub fn clang_Type_getNullability(type_: CXType) -> CXTypeNullabilityKind; + pub fn clang_Type_getObjCProtocolDecl(type_: CXType, index: c_uint) -> CXCursor; + /// Only available on `libclang` 8.0 and later. + #[cfg(feature = "clang_8_0")] + pub fn clang_Type_getObjCTypeArg(type_: CXType, index: c_uint) -> CXType; + pub fn clang_Type_getOffsetOf(type_: CXType, field: *const c_char) -> c_longlong; pub fn clang_Type_getSizeOf(type_: CXType) -> c_longlong; pub fn clang_Type_getTemplateArgumentAsType(type_: CXType, index: c_uint) -> CXType; /// Only available on `libclang` 11.0 and later. @@ -2097,16 +2118,17 @@ link! { pub fn clang_getSpecializedCursorTemplate(cursor: CXCursor) -> CXCursor; pub fn clang_getSpellingLocation(location: CXSourceLocation, file: *mut CXFile, line: *mut c_uint, column: *mut c_uint, offset: *mut c_uint); pub fn clang_getTUResourceUsageName(kind: CXTUResourceUsageKind) -> *const c_char; - /// Only available on `libclang` 5.0 and later. - #[cfg(feature = "clang_5_0")] - pub fn clang_getTranslationUnitTargetInfo(tu: CXTranslationUnit) -> CXTargetInfo; pub fn clang_getTemplateCursorKind(cursor: CXCursor) -> CXCursorKind; + pub fn clang_getToken(tu: CXTranslationUnit, location: CXSourceLocation) -> *mut CXToken; pub fn clang_getTokenExtent(tu: CXTranslationUnit, token: CXToken) -> CXSourceRange; pub fn clang_getTokenKind(token: CXToken) -> CXTokenKind; pub fn clang_getTokenLocation(tu: CXTranslationUnit, token: CXToken) -> CXSourceLocation; pub fn clang_getTokenSpelling(tu: CXTranslationUnit, token: CXToken) -> CXString; pub fn clang_getTranslationUnitCursor(tu: CXTranslationUnit) -> CXCursor; pub fn clang_getTranslationUnitSpelling(tu: CXTranslationUnit) -> CXString; + /// Only available on `libclang` 5.0 and later. + #[cfg(feature = "clang_5_0")] + pub fn clang_getTranslationUnitTargetInfo(tu: CXTranslationUnit) -> CXTargetInfo; pub fn clang_getTypeDeclaration(type_: CXType) -> CXCursor; pub fn clang_getTypeKindSpelling(type_: CXTypeKind) -> CXString; pub fn clang_getTypeSpelling(type_: CXType) -> CXString; @@ -2185,10 +2207,10 @@ link! { pub fn clang_Cursor_getParsedComment(C: CXCursor) -> CXComment; pub fn clang_FullComment_getAsHTML(comment: CXComment) -> CXString; pub fn clang_FullComment_getAsXML(comment: CXComment) -> CXString; - pub fn clang_HTMLStartTagComment_isSelfClosing(comment: CXComment) -> c_uint; pub fn clang_HTMLStartTag_getAttrName(comment: CXComment, index: c_uint) -> CXString; pub fn clang_HTMLStartTag_getAttrValue(comment: CXComment, index: c_uint) -> CXString; pub fn clang_HTMLStartTag_getNumAttrs(comment: CXComment) -> c_uint; + pub fn clang_HTMLStartTagComment_isSelfClosing(comment: CXComment) -> c_uint; pub fn clang_HTMLTagComment_getAsString(comment: CXComment) -> CXString; pub fn clang_HTMLTagComment_getTagName(comment: CXComment) -> CXString; pub fn clang_InlineCommandComment_getArgText(comment: CXComment, index: c_uint) -> CXString; @@ -2201,11 +2223,11 @@ link! { pub fn clang_ParamCommandComment_getParamName(comment: CXComment) -> CXString; pub fn clang_ParamCommandComment_isDirectionExplicit(comment: CXComment) -> c_uint; pub fn clang_ParamCommandComment_isParamIndexValid(comment: CXComment) -> c_uint; + pub fn clang_TextComment_getText(comment: CXComment) -> CXString; pub fn clang_TParamCommandComment_getDepth(comment: CXComment) -> c_uint; pub fn clang_TParamCommandComment_getIndex(comment: CXComment, depth: c_uint) -> c_uint; pub fn clang_TParamCommandComment_getParamName(comment: CXComment) -> CXString; pub fn clang_TParamCommandComment_isParamPositionValid(comment: CXComment) -> c_uint; - pub fn clang_TextComment_getText(comment: CXComment) -> CXString; pub fn clang_VerbatimBlockLineComment_getText(comment: CXComment) -> CXString; pub fn clang_VerbatimLineComment_getText(comment: CXComment) -> CXString; } diff --git a/src/link.rs b/src/link.rs index 64a3528..c3b0830 100644 --- a/src/link.rs +++ b/src/link.rs @@ -1,16 +1,4 @@ -// Copyright 2016 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 //================================================ // Macros diff --git a/src/support.rs b/src/support.rs index 8422f59..ff38d39 100644 --- a/src/support.rs +++ b/src/support.rs @@ -1,16 +1,4 @@ -// Copyright 2016 Kyle Mayes -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 //! Provides helper functionality. @@ -18,26 +6,13 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, io}; -use glob; +use glob::{self, Pattern}; use libc::c_int; use super::CXVersion; //================================================ -// Macros -//================================================ - -macro_rules! try_opt { - ($option:expr) => {{ - match $option { - Some(some) => some, - None => return None, - } - }}; -} - -//================================================ // Structs //================================================ @@ -74,20 +49,41 @@ impl Clang { /// directory returned by `llvm-config --bindir` is searched. On macOS /// systems, `xcodebuild -find clang` will next be queried. Last, the /// directories in the system's `PATH` are searched. + /// + /// ## Cross-compilation + /// + /// If target arguments are provided (e.g., `-target` followed by a target + /// like `x86_64-unknown-linux-gnu`) then this method will prefer a + /// target-prefixed instance of `clang` (e.g., + /// `x86_64-unknown-linux-gnu-clang` for the above example). pub fn find(path: Option<&Path>, args: &[String]) -> Option<Clang> { if let Ok(path) = env::var("CLANG_PATH") { return Some(Clang::new(path, args)); } + // Determine the cross-compilation target, if any. + + let mut target = None; + for i in 0..args.len() { + if args[i] == "-target" && i + 1 < args.len() { + target = Some(&args[i + 1]); + } + } + + // Collect the paths to search for a `clang` executable in. + let mut paths = vec![]; + if let Some(path) = path { paths.push(path.into()); } + if let Ok(path) = run_llvm_config(&["--bindir"]) { if let Some(line) = path.lines().next() { paths.push(line.into()); } } + if cfg!(target_os = "macos") { if let Ok((path, _)) = run("xcodebuild", &["-find", "clang"]) { if let Some(line) = path.lines().next() { @@ -95,7 +91,25 @@ impl Clang { } } } - paths.extend(env::split_paths(&env::var("PATH").unwrap())); + + if let Ok(path) = env::var("PATH") { + paths.extend(env::split_paths(&path)); + } + + // First, look for a target-prefixed `clang` executable. + + if let Some(target) = target { + let default = format!("{}-clang{}", target, env::consts::EXE_SUFFIX); + let versioned = format!("{}-clang-[0-9]*{}", target, env::consts::EXE_SUFFIX); + let patterns = &[&default[..], &versioned[..]]; + for path in &paths { + if let Some(path) = find(path, patterns) { + return Some(Clang::new(path, args)); + } + } + } + + // Otherwise, look for any other `clang` executable. let default = format!("clang{}", env::consts::EXE_SUFFIX); let versioned = format!("clang-[0-9]*{}", env::consts::EXE_SUFFIX); @@ -117,12 +131,17 @@ impl Clang { /// Returns the first match to the supplied glob patterns in the supplied /// directory if there are any matches. fn find(directory: &Path, patterns: &[&str]) -> Option<PathBuf> { + // Escape the directory in case it contains characters that have special + // meaning in glob patterns (e.g., `[` or `]`). + let directory = if let Some(directory) = directory.to_str() { + Path::new(&Pattern::escape(directory)).to_owned() + } else { + return None; + }; + for pattern in patterns { let pattern = directory.join(pattern).to_string_lossy().into_owned(); - if let Some(path) = try_opt!(glob::glob(&pattern).ok()) - .filter_map(|p| p.ok()) - .next() - { + if let Some(path) = glob::glob(&pattern).ok()?.filter_map(|p| p.ok()).next() { if path.is_file() && is_executable(&path).unwrap_or(false) { return Some(path); } @@ -184,10 +203,10 @@ fn parse_version_number(number: &str) -> Option<c_int> { /// Parses the version from the output of a `clang` executable if possible. fn parse_version(path: &Path) -> Option<CXVersion> { let output = run_clang(path, &["--version"]).0; - let start = try_opt!(output.find("version ")) + 8; - let mut numbers = try_opt!(output[start..].split_whitespace().next()).split('.'); - let major = try_opt!(numbers.next().and_then(parse_version_number)); - let minor = try_opt!(numbers.next().and_then(parse_version_number)); + let start = output.find("version ")? + 8; + let mut numbers = output[start..].split_whitespace().next()?.split('.'); + let major = numbers.next().and_then(parse_version_number)?; + let minor = numbers.next().and_then(parse_version_number)?; let subminor = numbers.next().and_then(parse_version_number).unwrap_or(0); Some(CXVersion { Major: major, @@ -201,8 +220,8 @@ fn parse_search_paths(path: &Path, language: &str, args: &[String]) -> Option<Ve let mut clang_args = vec!["-E", "-x", language, "-", "-v"]; clang_args.extend(args.iter().map(|s| &**s)); let output = run_clang(path, &clang_args).1; - let start = try_opt!(output.find("#include <...> search starts here:")) + 34; - let end = try_opt!(output.find("End of search list.")); + let start = output.find("#include <...> search starts here:")? + 34; + let end = output.find("End of search list.")?; let paths = output[start..end].replace("(framework directory)", ""); Some( paths diff --git a/tests/lib.rs b/tests/lib.rs index 100a6c6..b50055a 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -46,3 +46,10 @@ fn test_support() { let clang = support::Clang::find(None, &[]).unwrap(); println!("{:?}", clang); } + +#[test] +fn test_support_target() { + let args = &["-target".into(), "x86_64-unknown-linux-gnu".into()]; + let clang = support::Clang::find(None, args).unwrap(); + println!("{:?}", clang); +} |