|
| 1 | +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]> |
| 2 | +// SPDX-FileContributor: Be Wilson <[email protected]> |
| 3 | +// SPDX-FileContributor: pkg-config crate contributors |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: MIT OR Apache-2.0 |
| 6 | + |
| 7 | +//! This module contains code from the [pkg-config crate](https://github.com/rust-lang/pkg-config-rs). |
| 8 | +//! It has been decoupled from the pkg-config crate because qt-build-utils reads Qt's .prl files instead, which |
| 9 | +//! does not require a pkg-config executable to be available. |
| 10 | +
|
| 11 | +use std::env; |
| 12 | + |
| 13 | +/// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories) |
| 14 | +/// using target-specific logic. |
| 15 | +fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> { |
| 16 | + fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> { |
| 17 | + for suffix in suffixes { |
| 18 | + if let Some(lib_basename) = filename.strip_suffix(suffix) { |
| 19 | + return Some(lib_basename); |
| 20 | + } |
| 21 | + } |
| 22 | + None |
| 23 | + } |
| 24 | + |
| 25 | + let prefix = "lib"; |
| 26 | + #[allow(clippy::collapsible_else_if)] |
| 27 | + if target.contains("msvc") { |
| 28 | + // According to link.exe documentation: |
| 29 | + // https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170 |
| 30 | + // |
| 31 | + // LINK doesn't use file extensions to make assumptions about the contents of a file. |
| 32 | + // Instead, LINK examines each input file to determine what kind of file it is. |
| 33 | + // |
| 34 | + // However, rustc appends `.lib` to the string it receives from the -l command line argument, |
| 35 | + // which it receives from Cargo via cargo:rustc-link-lib: |
| 36 | + // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828 |
| 37 | + // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843 |
| 38 | + // So the only file extension that works for MSVC targets is `.lib` |
| 39 | + test_suffixes(filename, &[".lib"]) |
| 40 | + } else if target.contains("windows") && target.contains("gnu") { |
| 41 | + // GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc, |
| 42 | + // which tells rustc to use the GNU linker. rustc does not prepend/append to the string it |
| 43 | + // receives via the -l command line argument before passing it to the linker: |
| 44 | + // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446 |
| 45 | + // https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457 |
| 46 | + // GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs. |
| 47 | + // GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove |
| 48 | + // the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix. |
| 49 | + // https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll |
| 50 | + if let Some(filename) = filename.strip_prefix(prefix) { |
| 51 | + test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"]) |
| 52 | + } else { |
| 53 | + test_suffixes(filename, &[".dll.a", ".dll", ".lib"]) |
| 54 | + } |
| 55 | + } else if target.contains("apple") { |
| 56 | + if let Some(filename) = filename.strip_prefix(prefix) { |
| 57 | + test_suffixes(filename, &[".a", ".so", ".dylib"]) |
| 58 | + } else { |
| 59 | + None |
| 60 | + } |
| 61 | + } else { |
| 62 | + if let Some(filename) = filename.strip_prefix(prefix) { |
| 63 | + test_suffixes(filename, &[".a", ".so"]) |
| 64 | + } else { |
| 65 | + None |
| 66 | + } |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +/// Split link_args produced by pkg-config --cflags and / or --libs into separate flags. |
| 71 | +/// |
| 72 | +/// Backslash in link_args is used to preserve literal meaning of following byte. Different words are |
| 73 | +/// separated by unescaped space. Other whitespace characters generally should not occur unescaped |
| 74 | +/// at all, apart from the newline at the end of link_args. For compatibility with what others |
| 75 | +/// consumers of pkg-config link_args would do in this scenario, they are used here for splitting as |
| 76 | +/// well. |
| 77 | +fn split_flags(link_args: &[u8]) -> Vec<String> { |
| 78 | + let mut word = Vec::new(); |
| 79 | + let mut words = Vec::new(); |
| 80 | + let mut escaped = false; |
| 81 | + |
| 82 | + for &b in link_args { |
| 83 | + match b { |
| 84 | + _ if escaped => { |
| 85 | + escaped = false; |
| 86 | + word.push(b); |
| 87 | + } |
| 88 | + b'\\' => escaped = true, |
| 89 | + b'\t' | b'\n' | b'\r' | b' ' => { |
| 90 | + if !word.is_empty() { |
| 91 | + words.push(String::from_utf8(word).unwrap()); |
| 92 | + word = Vec::new(); |
| 93 | + } |
| 94 | + } |
| 95 | + _ => word.push(b), |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + if !word.is_empty() { |
| 100 | + words.push(String::from_utf8(word).unwrap()); |
| 101 | + } |
| 102 | + |
| 103 | + words |
| 104 | +} |
| 105 | + |
| 106 | +pub(crate) fn parse_libs_cflags(name: &str, link_args: &[u8]) { |
| 107 | + let mut is_msvc = false; |
| 108 | + let target = env::var("TARGET"); |
| 109 | + if let Ok(target) = &target { |
| 110 | + if target.contains("msvc") { |
| 111 | + is_msvc = true; |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + let words = split_flags(link_args); |
| 116 | + |
| 117 | + // Handle single-character arguments like `-I/usr/include` |
| 118 | + let parts = words |
| 119 | + .iter() |
| 120 | + .filter(|l| l.len() > 2) |
| 121 | + .map(|arg| (&arg[0..2], &arg[2..])); |
| 122 | + for (flag, val) in parts { |
| 123 | + match flag { |
| 124 | + "-L" => { |
| 125 | + println!("cargo:rustc-link-search=native={}", val); |
| 126 | + } |
| 127 | + "-F" => { |
| 128 | + println!("cargo:rustc-link-search=framework={}", val); |
| 129 | + } |
| 130 | + "-I" => (), |
| 131 | + "-l" => { |
| 132 | + // These are provided by the CRT with MSVC |
| 133 | + if is_msvc && ["m", "c", "pthread"].contains(&val) { |
| 134 | + continue; |
| 135 | + } |
| 136 | + |
| 137 | + println!("cargo:rustc-link-lib={}", val); |
| 138 | + } |
| 139 | + "-D" => (), |
| 140 | + _ => {} |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + // Handle multi-character arguments with space-separated value like `-framework foo` |
| 145 | + let mut iter = words.iter().flat_map(|arg| { |
| 146 | + if let Some(arg) = arg.strip_prefix("-Wl,") { |
| 147 | + arg.split(',').collect() |
| 148 | + } else { |
| 149 | + vec![arg.as_ref()] |
| 150 | + } |
| 151 | + }); |
| 152 | + while let Some(part) = iter.next() { |
| 153 | + match part { |
| 154 | + "-framework" => { |
| 155 | + if let Some(lib) = iter.next() { |
| 156 | + println!("cargo:rustc-link-lib=framework={}", lib); |
| 157 | + } |
| 158 | + } |
| 159 | + "-isystem" | "-iquote" | "-idirafter" => {} |
| 160 | + _ => { |
| 161 | + let path = std::path::Path::new(part); |
| 162 | + if path.is_file() { |
| 163 | + // Cargo doesn't have a means to directly specify a file path to link, |
| 164 | + // so split up the path into the parent directory and library name. |
| 165 | + // TODO: pass file path directly when link-arg library type is stabilized |
| 166 | + // https://github.com/rust-lang/rust/issues/99427 |
| 167 | + if let (Some(dir), Some(file_name), Ok(target)) = |
| 168 | + (path.parent(), path.file_name(), &target) |
| 169 | + { |
| 170 | + match extract_lib_from_filename(target, &file_name.to_string_lossy()) { |
| 171 | + Some(lib_basename) => { |
| 172 | + println!("cargo:rustc-link-search={}", dir.display()); |
| 173 | + println!("cargo:rustc-link-lib={}", lib_basename); |
| 174 | + } |
| 175 | + None => { |
| 176 | + println!("cargo:warning=File path {} found in .prl file for {}, but could not extract library base name to pass to linker command line", path.display(), name); |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + let linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,")); |
| 186 | + for option in linker_options { |
| 187 | + let mut pop = false; |
| 188 | + let mut ld_option = vec![]; |
| 189 | + for subopt in option[4..].split(',') { |
| 190 | + if pop { |
| 191 | + pop = false; |
| 192 | + continue; |
| 193 | + } |
| 194 | + |
| 195 | + if subopt == "-framework" { |
| 196 | + pop = true; |
| 197 | + continue; |
| 198 | + } |
| 199 | + |
| 200 | + ld_option.push(subopt); |
| 201 | + } |
| 202 | + |
| 203 | + println!("cargo:rustc-link-arg=-Wl,{}", ld_option.join(",")); |
| 204 | + } |
| 205 | +} |
0 commit comments