diff options
Diffstat (limited to 'build')
-rw-r--r-- | build/common.rs | 272 | ||||
-rw-r--r-- | build/dynamic.rs | 64 | ||||
-rw-r--r-- | build/static.rs | 69 |
3 files changed, 214 insertions, 191 deletions
diff --git a/build/common.rs b/build/common.rs index 56480df..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; @@ -22,87 +10,38 @@ use std::process::Command; 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", - // Scoop user installation https://scoop.sh/. - // Chocolatey, WinGet and other installers use to the system wide dir listed above - "C:\\Users\\*\\scoop\\apps\\llvm\\current\\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()) +/// 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, + )) + }); } -/// 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) -} - -/// 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, @@ -152,33 +91,117 @@ impl Drop for CommandErrorPrinter { } } -/// 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)> { - // Escape the directory in case it contains characters that have special - // meaning in glob patterns (e.g., `[` or `]`). + // 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 directory to the filename patterns to obtain the path patterns. + // Join the escaped directory to the filename glob patterns to obtain + // complete glob patterns for the files being searched for. let paths = filenames .iter() .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()?.to_str().unwrap(); + 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., @@ -193,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); @@ -212,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 env::split_paths(&path) { - found.extend(search_directories(&directory, files)); + 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 @@ -276,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 156afe4..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,8 +79,11 @@ 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 let Some(version) = filename.strip_prefix("libclang.so.") { version @@ -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(); diff --git a/build/static.rs b/build/static.rs index 66da274..6af914f 100644 --- a/build/static.rs +++ b/build/static.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,7 +8,28 @@ 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(); @@ -32,7 +41,7 @@ fn get_library_name(path: &Path) -> Option<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() @@ -50,24 +59,7 @@ 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> { // Escape the directory in case it contains characters that have special // meaning in glob patterns (e.g., `[` or `]`). @@ -84,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" @@ -100,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(); @@ -133,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"); |