Import 'clang-sys' package version 0.29.3
* Add OWNERS
* No Android.bp yet
Bug: 152884384
Test: make
Change-Id: I1d652df9a2f868b46957554a8ade394acbfe7af0
diff --git a/build/common.rs b/build/common.rs
new file mode 100644
index 0000000..958ef9b
--- /dev/null
+++ b/build/common.rs
@@ -0,0 +1,221 @@
+// 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.
+
+extern crate glob;
+
+use std::env;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+use glob::MatchOptions;
+
+/// `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",
+];
+
+/// Executes the supplied console command, returning the `stdout` output if the
+/// command was successfully executed.
+fn run_command(command: &str, arguments: &[&str]) -> Option<String> {
+ macro_rules! warn {
+ ($error:expr) => {
+ println!(
+ "cargo:warning=couldn't execute `{} {}` ({})",
+ command,
+ arguments.join(" "),
+ $error,
+ );
+ };
+ }
+
+ let output = match Command::new(command).args(arguments).output() {
+ Ok(output) => output,
+ Err(error) => {
+ warn!(format!("error: {}", error));
+ return None;
+ }
+ };
+
+ if !output.status.success() {
+ warn!(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());
+
+ let output = run_command(&path, arguments);
+ if output.is_none() {
+ println!(
+ "cargo:warning=set the LLVM_CONFIG_PATH environment variable to \
+ the full path to a valid `llvm-config` executable (including the \
+ executable itself)"
+ );
+ }
+
+ output
+}
+
+/// Returns the paths to and the filenames of the files matching the supplied
+/// filename patterns in the supplied directory.
+fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
+ // Join the directory to the filename patterns to obtain the path patterns.
+ let paths = filenames
+ .iter()
+ .filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned));
+
+ // Prevent wildcards from matching path separators.
+ 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![]
+ }
+ })
+ .filter_map(|p| {
+ let filename = p.file_name().and_then(|f| f.to_str())?;
+
+ // The `libclang_shared` library has been renamed to `libclang-cpp`
+ // in Clang 10. This can cause instances of this library (e.g.,
+ // `libclang-cpp.so.10`) to be matched by patterns looking for
+ // instances of `libclang`.
+ if filename.contains("-cpp.") {
+ return None;
+ }
+
+ Some((directory.to_owned(), filename.into()))
+ })
+ .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.
+fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
+ let mut results = search_directory(directory, filenames);
+
+ // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
+ // while `libclang.lib` is usually found in the LLVM `lib` directory. To
+ // keep things consistent with other platforms, only LLVM `lib` directories
+ // are included in the backup search directory globs so we need to search
+ // the LLVM `bin` directory here.
+ if cfg!(target_os = "windows") && directory.ends_with("lib") {
+ let sibling = directory.parent().unwrap().join("bin");
+ results.extend(search_directory(&sibling, filenames).into_iter());
+ }
+
+ 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.
+ 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.
+ if let Some(parent) = path.parent() {
+ let filename = path.file_name().unwrap().to_str().unwrap();
+ let libraries = search_directories(parent, files);
+ if libraries.iter().any(|(_, f)| f == filename) {
+ return vec![(parent.into(), filename.into())];
+ }
+ }
+
+ return search_directories(&path, files);
+ }
+
+ let mut found = vec![];
+
+ // Search the `bin` and `lib` directories in directory provided 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));
+ }
+
+ // Search the toolchain directory in the directory provided by
+ // `xcode-select --print-path`.
+ if cfg!(target_os = "macos") {
+ if let Some(output) = run_command("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));
+ }
+ }
+
+ // Search the directories provided by 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));
+ }
+ }
+
+ // Determine the `libclang` directory patterns.
+ let directories = if cfg!(any(target_os = "freebsd", target_os = "linux")) {
+ DIRECTORIES_LINUX
+ } else if cfg!(target_os = "macos") {
+ DIRECTORIES_MACOS
+ } else if cfg!(target_os = "windows") {
+ DIRECTORIES_WINDOWS
+ } else {
+ &[]
+ };
+
+ // Search the directories provided by the `libclang` directory patterns.
+ let mut options = MatchOptions::new();
+ options.case_sensitive = false;
+ options.require_literal_separator = true;
+ 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
+}
diff --git a/build/dynamic.rs b/build/dynamic.rs
new file mode 100644
index 0000000..2054ba0
--- /dev/null
+++ b/build/dynamic.rs
@@ -0,0 +1,244 @@
+// 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.
+
+use std::env;
+use std::fs::File;
+use std::io::{self, Error, ErrorKind, Read, Seek, SeekFrom};
+use std::path::{Path, PathBuf};
+
+use super::common;
+
+/// Returns the ELF class from the ELF header in the supplied file.
+fn parse_elf_header(path: &Path) -> io::Result<u8> {
+ let mut file = File::open(path)?;
+ let mut buffer = [0; 5];
+ file.read_exact(&mut buffer)?;
+ if buffer[..4] == [127, 69, 76, 70] {
+ Ok(buffer[4])
+ } else {
+ Err(Error::new(ErrorKind::InvalidData, "invalid ELF header"))
+ }
+}
+
+/// Returns the magic number from the PE header in the supplied file.
+fn parse_pe_header(path: &Path) -> io::Result<u16> {
+ let mut file = File::open(path)?;
+
+ // Determine 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.
+ 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.
+ 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")) {
+ let class = parse_elf_header(path).map_err(|e| e.to_string())?;
+
+ if cfg!(target_pointer_width = "32") && class != 1 {
+ return Err("invalid ELF class (64-bit)".into());
+ }
+
+ if cfg!(target_pointer_width = "64") && class != 2 {
+ return Err("invalid ELF class (32-bit)".into());
+ }
+
+ Ok(())
+ } else if cfg!(target_os = "windows") {
+ let magic = parse_pe_header(path).map_err(|e| e.to_string())?;
+
+ if cfg!(target_pointer_width = "32") && magic != 267 {
+ return Err("invalid DLL (64-bit)".into());
+ }
+
+ if cfg!(target_pointer_width = "64") && magic != 523 {
+ return Err("invalid DLL (32-bit)".into());
+ }
+
+ Ok(())
+ } else {
+ Ok(())
+ }
+}
+
+/// Returns the components of the version in the supplied `libclang` shared
+// library filename.
+fn parse_version(filename: &str) -> Vec<u32> {
+ let version = if filename.starts_with("libclang.so.") {
+ &filename[12..]
+ } else if filename.starts_with("libclang-") {
+ &filename[9..filename.len() - 3]
+ } else {
+ return vec![];
+ };
+
+ version.split('.').map(|s| s.parse().unwrap_or(0)).collect()
+}
+
+/// Returns the paths to, the filenames, and the versions of the `libclang`
+// shared libraries.
+fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Vec<u32>)>, String> {
+ let mut files = vec![format!(
+ "{}clang{}",
+ env::consts::DLL_PREFIX,
+ env::consts::DLL_SUFFIX
+ )];
+
+ if cfg!(target_os = "linux") {
+ // Some Linux distributions don't create a `libclang.so` symlink, so we
+ // need to look for versioned files (e.g., `libclang-3.9.so`).
+ files.push("libclang-*.so".into());
+
+ // Some Linux distributions don't create a `libclang.so` symlink and
+ // don't have versioned files as described above, so we need to look for
+ // suffix versioned files (e.g., `libclang.so.1`). However, `ld` cannot
+ // link to these files, so this will only be included when linking at
+ // runtime.
+ if runtime {
+ files.push("libclang.so.*".into());
+ files.push("libclang-*.so.*".into());
+ }
+ }
+
+ if cfg!(any(
+ target_os = "openbsd",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ )) {
+ // Some BSD distributions don't create a `libclang.so` symlink either,
+ // but use a different naming scheme for versioned files (e.g.,
+ // `libclang.so.7.0`).
+ files.push("libclang.so.*".into());
+ }
+
+ if cfg!(target_os = "windows") {
+ // The official LLVM build uses `libclang.dll` on Windows instead of
+ // `clang.dll`. However, unofficial builds such as MinGW use `clang.dll`.
+ files.push("libclang.dll".into());
+ }
+
+ // Validate the `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) {
+ Ok(()) => {
+ let version = parse_version(&filename);
+ valid.push((directory, filename, version))
+ }
+ Err(message) => invalid.push(format!("({}: {})", path.display(), message)),
+ }
+ }
+
+ if !valid.is_empty() {
+ return Ok(valid);
+ }
+
+ let message = format!(
+ "couldn't find any valid shared libraries matching: [{}], set the \
+ `LIBCLANG_PATH` environment variable to a path where one of these files \
+ can be found (invalid: [{}])",
+ files
+ .iter()
+ .map(|f| format!("'{}'", f))
+ .collect::<Vec<_>>()
+ .join(", "),
+ invalid.join(", "),
+ );
+
+ Err(message)
+}
+
+/// Returns the directory and filename of the "best" available `libclang` shared
+/// library.
+pub fn find(runtime: bool) -> Result<(PathBuf, String), String> {
+ search_libclang_directories(runtime)?
+ .iter()
+ .max_by_key(|f| &f.2)
+ .cloned()
+ .map(|(path, filename, _)| (path, filename))
+ .ok_or_else(|| "unreachable".into())
+}
+
+/// Find and link to `libclang` dynamically.
+#[cfg(not(feature = "runtime"))]
+pub fn link() {
+ use std::fs;
+
+ let (directory, filename) = find(false).unwrap();
+ println!("cargo:rustc-link-search={}", directory.display());
+
+ if cfg!(all(target_os = "windows", target_env = "msvc")) {
+ // Find the `libclang` stub static library required for the MSVC
+ // toolchain.
+ let lib = if !directory.ends_with("bin") {
+ directory
+ } else {
+ directory.parent().unwrap().join("lib")
+ };
+
+ if lib.join("libclang.lib").exists() {
+ println!("cargo:rustc-link-search={}", lib.display());
+ } else if lib.join("libclang.dll.a").exists() {
+ // MSYS and MinGW use `libclang.dll.a` instead of `libclang.lib`.
+ // It is linkable with the MSVC linker, but Rust doesn't recognize
+ // the `.a` suffix, so we need to copy it with a different name.
+ //
+ // FIXME: Maybe we can just hardlink or symlink it?
+ let out = env::var("OUT_DIR").unwrap();
+ fs::copy(
+ lib.join("libclang.dll.a"),
+ Path::new(&out).join("libclang.lib"),
+ )
+ .unwrap();
+ println!("cargo:rustc-link-search=native={}", out);
+ } else {
+ panic!(
+ "using '{}', so 'libclang.lib' or 'libclang.dll.a' must be \
+ available in {}",
+ filename,
+ lib.display(),
+ );
+ }
+
+ println!("cargo:rustc-link-lib=dylib=libclang");
+ } else {
+ let name = filename.trim_start_matches("lib");
+
+ // Strip extensions and trailing version numbers (e.g., the `.so.7.0` in
+ // `libclang.so.7.0`).
+ let name = match name.find(".dylib").or_else(|| name.find(".so")) {
+ Some(index) => &name[0..index],
+ None => &name,
+ };
+
+ println!("cargo:rustc-link-lib=dylib={}", name);
+ }
+}
diff --git a/build/static.rs b/build/static.rs
new file mode 100644
index 0000000..bacd4ad
--- /dev/null
+++ b/build/static.rs
@@ -0,0 +1,136 @@
+// 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.
+
+extern crate glob;
+
+use std::path::{Path, PathBuf};
+
+use common;
+
+/// Returns the name of an LLVM or Clang library from a path to such a library.
+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()
+ } else {
+ string.to_string()
+ }
+ })
+}
+
+/// Returns the LLVM libraries required to link to `libclang` statically.
+fn get_llvm_libraries() -> Vec<String> {
+ common::run_llvm_config(&["--libs"])
+ .unwrap()
+ .split_whitespace()
+ .filter_map(|p| {
+ // 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())
+ } else {
+ get_library_name(Path::new(p))
+ }
+ })
+ .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.
+fn get_clang_libraries<P: AsRef<Path>>(directory: P) -> Vec<String> {
+ let pattern = directory
+ .as_ref()
+ .join("libclang*.a")
+ .to_string_lossy()
+ .to_string();
+ if let Ok(libraries) = glob::glob(&pattern) {
+ libraries
+ .filter_map(|l| l.ok().and_then(|l| get_library_name(&l)))
+ .collect()
+ } else {
+ CLANG_LIBRARIES.iter().map(|l| (*l).to_string()).collect()
+ }
+}
+
+/// Returns a directory containing `libclang` static libraries.
+fn find() -> PathBuf {
+ let name = if cfg!(target_os = "windows") {
+ "libclang.lib"
+ } else {
+ "libclang.a"
+ };
+
+ let files = common::search_libclang_directories(&[name.into()], "LIBCLANG_STATIC_PATH");
+ if let Some((directory, _)) = files.into_iter().nth(0) {
+ directory
+ } else {
+ panic!("could not find any static libraries");
+ }
+}
+
+/// Find and link to `libclang` statically.
+pub fn link() {
+ let directory = find();
+
+ // Specify required Clang static libraries.
+ println!("cargo:rustc-link-search=native={}", directory.display());
+ for library in get_clang_libraries(directory) {
+ println!("cargo:rustc-link-lib=static={}", library);
+ }
+
+ // Determine the shared mode used by LLVM.
+ let mode = common::run_llvm_config(&["--shared-mode"]).map(|m| m.trim().to_owned());
+ let prefix = if mode.map_or(false, |m| m == "static") {
+ "static="
+ } else {
+ ""
+ };
+
+ // Specify required LLVM static libraries.
+ println!(
+ "cargo:rustc-link-search=native={}",
+ common::run_llvm_config(&["--libdir"]).unwrap().trim_end()
+ );
+ for library in get_llvm_libraries() {
+ println!("cargo:rustc-link-lib={}{}", prefix, library);
+ }
+
+ // Specify required system libraries.
+ // 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") {
+ 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");
+ }
+}