mirror of
https://github.com/microsoft/edit.git
synced 2025-07-07 21:35:16 +00:00
Make the ICU SONAME configurable (#495)
This PR was tested on Ubuntu with: ``` EDIT_CFG_ICUUC_SONAME=libicuuc.so.74 EDIT_CFG_ICUI18N_SONAME=libicui18n.so.74 EDIT_CFG_ICU_RENAMING_VERSION=74 cargo build --config .cargo/release.toml --release ``` Search & Replace now works flawlessly. I hope that package maintainers will be able to make use of this when ingesting future versions of Edit. Closes #172
This commit is contained in:
parent
70f5b73878
commit
b277a1e67b
7 changed files with 317 additions and 81 deletions
50
README.md
50
README.md
|
@ -19,14 +19,6 @@ You can install the latest version with WinGet:
|
||||||
winget install Microsoft.Edit
|
winget install Microsoft.Edit
|
||||||
```
|
```
|
||||||
|
|
||||||
### Notes to Package Maintainers
|
|
||||||
|
|
||||||
The canonical executable name is "edit" and the alternative name is "msedit".
|
|
||||||
|
|
||||||
We're aware of the potential conflict of "edit" with existing commands and as such recommend naming packages and executables "msedit".
|
|
||||||
Names such as "ms-edit" should be avoided.
|
|
||||||
Assigning an "edit" alias is recommended if possible.
|
|
||||||
|
|
||||||
## Build Instructions
|
## Build Instructions
|
||||||
|
|
||||||
* [Install Rust](https://www.rust-lang.org/tools/install)
|
* [Install Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
@ -34,3 +26,45 @@ Assigning an "edit" alias is recommended if possible.
|
||||||
* Alternatively, set the environment variable `RUSTC_BOOTSTRAP=1`
|
* Alternatively, set the environment variable `RUSTC_BOOTSTRAP=1`
|
||||||
* Clone the repository
|
* Clone the repository
|
||||||
* For a release build, run: `cargo build --config .cargo/release.toml --release`
|
* For a release build, run: `cargo build --config .cargo/release.toml --release`
|
||||||
|
|
||||||
|
## Notes to Package Maintainers
|
||||||
|
|
||||||
|
### Package Naming
|
||||||
|
|
||||||
|
The canonical executable name is "edit" and the alternative name is "msedit".
|
||||||
|
We're aware of the potential conflict of "edit" with existing commands and recommend alternatively naming packages and executables "msedit".
|
||||||
|
Names such as "ms-edit" should be avoided.
|
||||||
|
Assigning an "edit" alias is recommended, if possible.
|
||||||
|
|
||||||
|
### ICU library name (SONAME)
|
||||||
|
|
||||||
|
This project _optionally_ depends on the ICU library for its Search and Replace functionality.
|
||||||
|
By default, the project will look for a SONAME without version suffix:
|
||||||
|
* Windows: `icuuc.dll`
|
||||||
|
* macOS: `libicuuc.dylib`
|
||||||
|
* UNIX, and other OS: `libicuuc.so`
|
||||||
|
|
||||||
|
If your installation uses a different SONAME, please set the following environment variable at build time:
|
||||||
|
* `EDIT_CFG_ICUUC_SONAME`:
|
||||||
|
For instance, `libicuuc.so.76`.
|
||||||
|
* `EDIT_CFG_ICUI18N_SONAME`:
|
||||||
|
For instance, `libicui18n.so.76`.
|
||||||
|
|
||||||
|
Additionally, this project assumes that the ICU exports are exported without `_` prefix and without version suffix, such as `u_errorName`.
|
||||||
|
If your installation uses versioned exports, please set:
|
||||||
|
* `EDIT_CFG_ICU_CPP_EXPORTS`:
|
||||||
|
If set to `true`, it'll look for C++ symbols such as `_u_errorName`.
|
||||||
|
Enabled by default on macOS.
|
||||||
|
* `EDIT_CFG_ICU_RENAMING_VERSION`:
|
||||||
|
If set to a version number, such as `76`, it'll look for symbols such as `u_errorName_76`.
|
||||||
|
|
||||||
|
Finally, you can set the following environment variables:
|
||||||
|
* `EDIT_CFG_ICU_RENAMING_AUTO_DETECT`:
|
||||||
|
If set to `true`, the executable will try to detect the `EDIT_CFG_ICU_RENAMING_VERSION` value at runtime.
|
||||||
|
The way it does this is not officially supported by ICU and as such is not recommended to be relied upon.
|
||||||
|
Enabled by default on UNIX (excluding macOS) if no other options are set.
|
||||||
|
|
||||||
|
To test your settings, run `cargo test` again but with the `--ignored` flag. For instance:
|
||||||
|
```sh
|
||||||
|
cargo test -- --ignored
|
||||||
|
```
|
||||||
|
|
89
build.rs
89
build.rs
|
@ -1,9 +1,86 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
use core::panic;
|
||||||
|
use std::env::VarError;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
enum TargetOs {
|
||||||
|
Windows,
|
||||||
|
MacOS,
|
||||||
|
Unix,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let target_os = match env_opt("CARGO_CFG_TARGET_OS").as_str() {
|
||||||
|
"windows" => TargetOs::Windows,
|
||||||
|
"macos" | "ios" => TargetOs::MacOS,
|
||||||
|
_ => TargetOs::Unix,
|
||||||
|
};
|
||||||
|
let icuuc_soname = env_opt("EDIT_CFG_ICUUC_SONAME");
|
||||||
|
let icui18n_soname = env_opt("EDIT_CFG_ICUI18N_SONAME");
|
||||||
|
let cpp_exports = env_opt("EDIT_CFG_ICU_CPP_EXPORTS");
|
||||||
|
let renaming_version = env_opt("EDIT_CFG_ICU_RENAMING_VERSION");
|
||||||
|
let renaming_auto_detect = env_opt("EDIT_CFG_ICU_RENAMING_AUTO_DETECT");
|
||||||
|
|
||||||
|
// If none of the `EDIT_CFG_ICU*` environment variables are set,
|
||||||
|
// we default to enabling `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` on UNIX.
|
||||||
|
// This slightly improves portability at least in the cases where the SONAMEs match our defaults.
|
||||||
|
let renaming_auto_detect = if !renaming_auto_detect.is_empty() {
|
||||||
|
renaming_auto_detect.parse::<bool>().unwrap()
|
||||||
|
} else {
|
||||||
|
target_os == TargetOs::Unix
|
||||||
|
&& icuuc_soname.is_empty()
|
||||||
|
&& icui18n_soname.is_empty()
|
||||||
|
&& cpp_exports.is_empty()
|
||||||
|
&& renaming_version.is_empty()
|
||||||
|
};
|
||||||
|
if renaming_auto_detect && !renaming_version.is_empty() {
|
||||||
|
// It makes no sense to specify an explicit version and also ask for auto-detection.
|
||||||
|
panic!(
|
||||||
|
"Either `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` or `EDIT_CFG_ICU_RENAMING_VERSION` must be set, but not both"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let icuuc_soname = if !icuuc_soname.is_empty() {
|
||||||
|
&icuuc_soname
|
||||||
|
} else {
|
||||||
|
match target_os {
|
||||||
|
TargetOs::Windows => "icuuc.dll",
|
||||||
|
TargetOs::MacOS => "libicucore.dylib",
|
||||||
|
TargetOs::Unix => "libicuuc.so",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let icui18n_soname = if !icui18n_soname.is_empty() {
|
||||||
|
&icui18n_soname
|
||||||
|
} else {
|
||||||
|
match target_os {
|
||||||
|
TargetOs::Windows => "icuin.dll",
|
||||||
|
TargetOs::MacOS => "libicucore.dylib",
|
||||||
|
TargetOs::Unix => "libicui18n.so",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let icu_export_prefix =
|
||||||
|
if !cpp_exports.is_empty() && cpp_exports.parse::<bool>().unwrap() { "_" } else { "" };
|
||||||
|
let icu_export_suffix =
|
||||||
|
if !renaming_version.is_empty() { format!("_{renaming_version}") } else { String::new() };
|
||||||
|
|
||||||
|
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUUC_SONAME");
|
||||||
|
println!("cargo::rustc-env=EDIT_CFG_ICUUC_SONAME={icuuc_soname}");
|
||||||
|
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUI18N_SONAME");
|
||||||
|
println!("cargo::rustc-env=EDIT_CFG_ICUI18N_SONAME={icui18n_soname}");
|
||||||
|
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_PREFIX");
|
||||||
|
println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_PREFIX={icu_export_prefix}");
|
||||||
|
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_SUFFIX");
|
||||||
|
println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_SUFFIX={icu_export_suffix}");
|
||||||
|
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_RENAMING_AUTO_DETECT");
|
||||||
|
println!("cargo::rustc-check-cfg=cfg(edit_icu_renaming_auto_detect)");
|
||||||
|
if renaming_auto_detect {
|
||||||
|
println!("cargo::rustc-cfg=edit_icu_renaming_auto_detect");
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
|
if target_os == TargetOs::Windows {
|
||||||
winresource::WindowsResource::new()
|
winresource::WindowsResource::new()
|
||||||
.set_manifest_file("src/bin/edit/edit.exe.manifest")
|
.set_manifest_file("src/bin/edit/edit.exe.manifest")
|
||||||
.set("FileDescription", "Microsoft Edit")
|
.set("FileDescription", "Microsoft Edit")
|
||||||
|
@ -13,3 +90,13 @@ fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env_opt(name: &str) -> String {
|
||||||
|
match std::env::var(name) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(VarError::NotPresent) => String::new(),
|
||||||
|
Err(VarError::NotUnicode(_)) => {
|
||||||
|
panic!("Environment variable `{name}` is not valid Unicode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -79,6 +79,10 @@ impl Arena {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.base == NonNull::dangling()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn offset(&self) -> usize {
|
pub fn offset(&self) -> usize {
|
||||||
self.offset.get()
|
self.offset.get()
|
||||||
}
|
}
|
||||||
|
@ -171,7 +175,7 @@ impl Arena {
|
||||||
|
|
||||||
impl Drop for Arena {
|
impl Drop for Arena {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.base != NonNull::dangling() {
|
if !self.is_empty() {
|
||||||
unsafe { sys::virtual_release(self.base, self.capacity) };
|
unsafe { sys::virtual_release(self.base, self.capacity) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ pub fn init(capacity: usize) -> apperr::Result<()> {
|
||||||
/// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`.
|
/// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`.
|
||||||
pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> {
|
pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
#[cfg(test)]
|
||||||
|
if S_SCRATCH[0].is_empty() {
|
||||||
|
init(128 * 1024 * 1024).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let conflict = conflict.map(|a| a.delegate_target_unchecked());
|
let conflict = conflict.map(|a| a.delegate_target_unchecked());
|
||||||
|
|
||||||
|
|
91
src/icu.rs
91
src/icu.rs
|
@ -4,7 +4,7 @@
|
||||||
//! Bindings to the ICU library.
|
//! Bindings to the ICU library.
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ffi::CStr;
|
use std::ffi::{CStr, c_char};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
@ -922,33 +922,40 @@ struct LibraryFunctions {
|
||||||
uregex_end64: icu_ffi::uregex_end64,
|
uregex_end64: icu_ffi::uregex_end64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! proc_name {
|
||||||
|
($s:literal) => {
|
||||||
|
concat!(env!("EDIT_CFG_ICU_EXPORT_PREFIX"), $s, env!("EDIT_CFG_ICU_EXPORT_SUFFIX"), "\0")
|
||||||
|
.as_ptr() as *const c_char
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Found in libicuuc.so on UNIX, icuuc.dll/icu.dll on Windows.
|
// Found in libicuuc.so on UNIX, icuuc.dll/icu.dll on Windows.
|
||||||
const LIBICUUC_PROC_NAMES: [&CStr; 10] = [
|
const LIBICUUC_PROC_NAMES: [*const c_char; 10] = [
|
||||||
c"u_errorName",
|
proc_name!("u_errorName"),
|
||||||
c"ucasemap_open",
|
proc_name!("ucasemap_open"),
|
||||||
c"ucasemap_utf8FoldCase",
|
proc_name!("ucasemap_utf8FoldCase"),
|
||||||
c"ucnv_getAvailableName",
|
proc_name!("ucnv_getAvailableName"),
|
||||||
c"ucnv_getStandardName",
|
proc_name!("ucnv_getStandardName"),
|
||||||
c"ucnv_open",
|
proc_name!("ucnv_open"),
|
||||||
c"ucnv_close",
|
proc_name!("ucnv_close"),
|
||||||
c"ucnv_convertEx",
|
proc_name!("ucnv_convertEx"),
|
||||||
c"utext_setup",
|
proc_name!("utext_setup"),
|
||||||
c"utext_close",
|
proc_name!("utext_close"),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows.
|
// Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows.
|
||||||
const LIBICUI18N_PROC_NAMES: [&CStr; 11] = [
|
const LIBICUI18N_PROC_NAMES: [*const c_char; 11] = [
|
||||||
c"ucol_open",
|
proc_name!("ucol_open"),
|
||||||
c"ucol_strcollUTF8",
|
proc_name!("ucol_strcollUTF8"),
|
||||||
c"uregex_open",
|
proc_name!("uregex_open"),
|
||||||
c"uregex_close",
|
proc_name!("uregex_close"),
|
||||||
c"uregex_setTimeLimit",
|
proc_name!("uregex_setTimeLimit"),
|
||||||
c"uregex_setUText",
|
proc_name!("uregex_setUText"),
|
||||||
c"uregex_reset64",
|
proc_name!("uregex_reset64"),
|
||||||
c"uregex_findNext",
|
proc_name!("uregex_findNext"),
|
||||||
c"uregex_groupCount",
|
proc_name!("uregex_groupCount"),
|
||||||
c"uregex_start64",
|
proc_name!("uregex_start64"),
|
||||||
c"uregex_end64",
|
proc_name!("uregex_end64"),
|
||||||
];
|
];
|
||||||
|
|
||||||
enum LibraryFunctionsState {
|
enum LibraryFunctionsState {
|
||||||
|
@ -971,10 +978,7 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
|
||||||
unsafe {
|
unsafe {
|
||||||
LIBRARY_FUNCTIONS = LibraryFunctionsState::Failed;
|
LIBRARY_FUNCTIONS = LibraryFunctionsState::Failed;
|
||||||
|
|
||||||
let Ok(libicuuc) = sys::load_libicuuc() else {
|
let Ok(icu) = sys::load_icu() else {
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Ok(libicui18n) = sys::load_libicui18n() else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -998,25 +1002,26 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
|
||||||
let mut funcs = MaybeUninit::<LibraryFunctions>::uninit();
|
let mut funcs = MaybeUninit::<LibraryFunctions>::uninit();
|
||||||
let mut ptr = funcs.as_mut_ptr() as *mut TransparentFunction;
|
let mut ptr = funcs.as_mut_ptr() as *mut TransparentFunction;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(edit_icu_renaming_auto_detect)]
|
||||||
let scratch_outer = scratch_arena(None);
|
let scratch_outer = scratch_arena(None);
|
||||||
#[cfg(unix)]
|
#[cfg(edit_icu_renaming_auto_detect)]
|
||||||
let suffix = sys::icu_proc_suffix(&scratch_outer, libicuuc);
|
let suffix = sys::icu_detect_renaming_suffix(&scratch_outer, icu.libicuuc);
|
||||||
|
|
||||||
for (handle, names) in
|
for (handle, names) in [
|
||||||
[(libicuuc, &LIBICUUC_PROC_NAMES[..]), (libicui18n, &LIBICUI18N_PROC_NAMES[..])]
|
(icu.libicuuc, &LIBICUUC_PROC_NAMES[..]),
|
||||||
{
|
(icu.libicui18n, &LIBICUI18N_PROC_NAMES[..]),
|
||||||
for name in names {
|
] {
|
||||||
#[cfg(unix)]
|
for &name in names {
|
||||||
|
#[cfg(edit_icu_renaming_auto_detect)]
|
||||||
let scratch = scratch_arena(Some(&scratch_outer));
|
let scratch = scratch_arena(Some(&scratch_outer));
|
||||||
#[cfg(unix)]
|
#[cfg(edit_icu_renaming_auto_detect)]
|
||||||
let name = &sys::add_icu_proc_suffix(&scratch, name, &suffix);
|
let name = sys::icu_add_renaming_suffix(&scratch, name, &suffix);
|
||||||
|
|
||||||
let Ok(func) = sys::get_proc_address(handle, name) else {
|
let Ok(func) = sys::get_proc_address(handle, name) else {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
false,
|
false,
|
||||||
"Failed to load ICU function: {}",
|
"Failed to load ICU function: {:?}",
|
||||||
name.to_string_lossy()
|
CStr::from_ptr(name)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -1314,6 +1319,12 @@ mod icu_ffi {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[ignore]
|
||||||
|
#[test]
|
||||||
|
fn init() {
|
||||||
|
assert!(init_if_needed().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compare_strings_ascii() {
|
fn test_compare_strings_ascii() {
|
||||||
// Empty strings
|
// Empty strings
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
//! Read the `windows` module for reference.
|
//! Read the `windows` module for reference.
|
||||||
//! TODO: This reminds me that the sys API should probably be a trait.
|
//! TODO: This reminds me that the sys API should probably be a trait.
|
||||||
|
|
||||||
use std::ffi::{CStr, c_int, c_void};
|
use std::ffi::{CStr, c_char, c_int, c_void};
|
||||||
use std::fs::{self, File};
|
use std::fs::File;
|
||||||
use std::mem::{self, ManuallyDrop, MaybeUninit};
|
use std::mem::{self, ManuallyDrop, MaybeUninit};
|
||||||
use std::os::fd::{AsRawFd as _, FromRawFd as _};
|
use std::os::fd::{AsRawFd as _, FromRawFd as _};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -433,9 +433,9 @@ pub unsafe fn virtual_commit(base: NonNull<u8>, size: usize) -> apperr::Result<(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn load_library(name: &CStr) -> apperr::Result<NonNull<c_void>> {
|
unsafe fn load_library(name: *const c_char) -> apperr::Result<NonNull<c_void>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
NonNull::new(libc::dlopen(name.as_ptr(), libc::RTLD_LAZY))
|
NonNull::new(libc::dlopen(name, libc::RTLD_LAZY))
|
||||||
.ok_or_else(|| errno_to_apperr(libc::ENOENT))
|
.ok_or_else(|| errno_to_apperr(libc::ENOENT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -448,9 +448,12 @@ unsafe fn load_library(name: &CStr) -> apperr::Result<NonNull<c_void>> {
|
||||||
/// of the function you're loading. No type checks whatsoever are performed.
|
/// of the function you're loading. No type checks whatsoever are performed.
|
||||||
//
|
//
|
||||||
// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
|
// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
|
||||||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apperr::Result<T> {
|
pub unsafe fn get_proc_address<T>(
|
||||||
|
handle: NonNull<c_void>,
|
||||||
|
name: *const c_char,
|
||||||
|
) -> apperr::Result<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let sym = libc::dlsym(handle.as_ptr(), name.as_ptr());
|
let sym = libc::dlsym(handle.as_ptr(), name);
|
||||||
if sym.is_null() {
|
if sym.is_null() {
|
||||||
Err(errno_to_apperr(libc::ENOENT))
|
Err(errno_to_apperr(libc::ENOENT))
|
||||||
} else {
|
} else {
|
||||||
|
@ -459,20 +462,46 @@ pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_libicuuc() -> apperr::Result<NonNull<c_void>> {
|
pub struct LibIcu {
|
||||||
unsafe { load_library(c"libicuuc.so") }
|
pub libicuuc: NonNull<c_void>,
|
||||||
|
pub libicui18n: NonNull<c_void>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_libicui18n() -> apperr::Result<NonNull<c_void>> {
|
pub fn load_icu() -> apperr::Result<LibIcu> {
|
||||||
unsafe { load_library(c"libicui18n.so") }
|
const fn const_str_eq(a: &str, b: &str) -> bool {
|
||||||
}
|
let a = a.as_bytes();
|
||||||
|
let b = b.as_bytes();
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if i >= a.len() || i >= b.len() {
|
||||||
|
return a.len() == b.len();
|
||||||
|
}
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIBICUUC: &str = concat!(env!("EDIT_CFG_ICUUC_SONAME"), "\0");
|
||||||
|
const LIBICUI18N: &str = concat!(env!("EDIT_CFG_ICUI18N_SONAME"), "\0");
|
||||||
|
|
||||||
|
if const { const_str_eq(LIBICUUC, LIBICUI18N) } {
|
||||||
|
let icu = unsafe { load_library(LIBICUUC.as_ptr() as *const _)? };
|
||||||
|
Ok(LibIcu { libicuuc: icu, libicui18n: icu })
|
||||||
|
} else {
|
||||||
|
let libicuuc = unsafe { load_library(LIBICUUC.as_ptr() as *const _)? };
|
||||||
|
let libicui18n = unsafe { load_library(LIBICUI18N.as_ptr() as *const _)? };
|
||||||
|
Ok(LibIcu { libicuuc, libicui18n })
|
||||||
|
}
|
||||||
|
}
|
||||||
/// ICU, by default, adds the major version as a suffix to each exported symbol.
|
/// ICU, by default, adds the major version as a suffix to each exported symbol.
|
||||||
/// They also recommend to disable this for system-level installations (`runConfigureICU Linux --disable-renaming`),
|
/// They also recommend to disable this for system-level installations (`runConfigureICU Linux --disable-renaming`),
|
||||||
/// but I found that many (most?) Linux distributions don't do this for some reason.
|
/// but I found that many (most?) Linux distributions don't do this for some reason.
|
||||||
/// This function returns the suffix, if any.
|
/// This function returns the suffix, if any.
|
||||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
#[cfg(edit_icu_renaming_auto_detect)]
|
||||||
pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_> {
|
pub fn icu_detect_renaming_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_> {
|
||||||
unsafe {
|
unsafe {
|
||||||
type T = *const c_void;
|
type T = *const c_void;
|
||||||
|
|
||||||
|
@ -480,7 +509,7 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||||
|
|
||||||
// Check if the ICU library is using unversioned symbols.
|
// Check if the ICU library is using unversioned symbols.
|
||||||
// Return an empty suffix in that case.
|
// Return an empty suffix in that case.
|
||||||
if get_proc_address::<T>(handle, c"u_errorName").is_ok() {
|
if get_proc_address::<T>(handle, c"u_errorName".as_ptr()).is_ok() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +517,7 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||||
// this symbol seems to be always present. This allows us to call `dladdr`.
|
// this symbol seems to be always present. This allows us to call `dladdr`.
|
||||||
// It's the `UCaseMap::~UCaseMap()` destructor which for some reason isn't
|
// It's the `UCaseMap::~UCaseMap()` destructor which for some reason isn't
|
||||||
// in a namespace. Thank you ICU maintainers for this oversight.
|
// in a namespace. Thank you ICU maintainers for this oversight.
|
||||||
let proc = match get_proc_address::<T>(handle, c"_ZN8UCaseMapD1Ev") {
|
let proc = match get_proc_address::<T>(handle, c"_ZN8UCaseMapD1Ev".as_ptr()) {
|
||||||
Ok(proc) => proc,
|
Ok(proc) => proc,
|
||||||
Err(_) => return res,
|
Err(_) => return res,
|
||||||
};
|
};
|
||||||
|
@ -506,7 +535,7 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||||
Err(_) => return res,
|
Err(_) => return res,
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = match fs::read_link(path) {
|
let path = match std::fs::read_link(path) {
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(_) => path.into(),
|
Err(_) => path.into(),
|
||||||
};
|
};
|
||||||
|
@ -528,7 +557,13 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_icu_proc_suffix<'a, 'b, 'r>(arena: &'a Arena, name: &'b CStr, suffix: &str) -> &'r CStr
|
#[cfg(edit_icu_renaming_auto_detect)]
|
||||||
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||||
|
pub fn icu_add_renaming_suffix<'a, 'b, 'r>(
|
||||||
|
arena: &'a Arena,
|
||||||
|
name: *const c_char,
|
||||||
|
suffix: &str,
|
||||||
|
) -> *const c_char
|
||||||
where
|
where
|
||||||
'a: 'r,
|
'a: 'r,
|
||||||
'b: 'r,
|
'b: 'r,
|
||||||
|
@ -538,16 +573,15 @@ where
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: In this particular case we know that the string
|
// SAFETY: In this particular case we know that the string
|
||||||
// is valid UTF-8, because it comes from icu.rs.
|
// is valid UTF-8, because it comes from icu.rs.
|
||||||
|
let name = unsafe { CStr::from_ptr(name) };
|
||||||
let name = unsafe { name.to_str().unwrap_unchecked() };
|
let name = unsafe { name.to_str().unwrap_unchecked() };
|
||||||
|
|
||||||
let mut res = ArenaString::new_in(arena);
|
let mut res = ManuallyDrop::new(ArenaString::new_in(arena));
|
||||||
res.reserve(name.len() + suffix.len() + 1);
|
res.reserve(name.len() + suffix.len() + 1);
|
||||||
res.push_str(name);
|
res.push_str(name);
|
||||||
res.push_str(suffix);
|
res.push_str(suffix);
|
||||||
res.push('\0');
|
res.push('\0');
|
||||||
|
res.as_ptr() as *const c_char
|
||||||
let bytes: &'a [u8] = unsafe { mem::transmute(res.as_bytes()) };
|
|
||||||
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use std::ffi::{CStr, OsString, c_void};
|
use std::ffi::{OsString, c_char, c_void};
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
@ -20,6 +20,35 @@ use crate::apperr;
|
||||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||||
use crate::helpers::*;
|
use crate::helpers::*;
|
||||||
|
|
||||||
|
macro_rules! w_env {
|
||||||
|
($s:literal) => {{
|
||||||
|
const INPUT: &[u8] = env!($s).as_bytes();
|
||||||
|
const OUTPUT_LEN: usize = windows_sys::core::utf16_len(INPUT) + 1;
|
||||||
|
const OUTPUT: &[u16; OUTPUT_LEN] = {
|
||||||
|
let mut buffer = [0; OUTPUT_LEN];
|
||||||
|
let mut input_pos = 0;
|
||||||
|
let mut output_pos = 0;
|
||||||
|
while let Some((mut code_point, new_pos)) =
|
||||||
|
windows_sys::core::decode_utf8_char(INPUT, input_pos)
|
||||||
|
{
|
||||||
|
input_pos = new_pos;
|
||||||
|
if code_point <= 0xffff {
|
||||||
|
buffer[output_pos] = code_point as u16;
|
||||||
|
output_pos += 1;
|
||||||
|
} else {
|
||||||
|
code_point -= 0x10000;
|
||||||
|
buffer[output_pos] = 0xd800 + (code_point >> 10) as u16;
|
||||||
|
output_pos += 1;
|
||||||
|
buffer[output_pos] = 0xdc00 + (code_point & 0x3ff) as u16;
|
||||||
|
output_pos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&{ buffer }
|
||||||
|
};
|
||||||
|
OUTPUT.as_ptr()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
type ReadConsoleInputExW = unsafe extern "system" fn(
|
type ReadConsoleInputExW = unsafe extern "system" fn(
|
||||||
h_console_input: Foundation::HANDLE,
|
h_console_input: Foundation::HANDLE,
|
||||||
lp_buffer: *mut Console::INPUT_RECORD,
|
lp_buffer: *mut Console::INPUT_RECORD,
|
||||||
|
@ -109,7 +138,10 @@ pub fn init() -> apperr::Result<Deinit> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn load_read_func(module: *const u16) -> apperr::Result<ReadConsoleInputExW> {
|
unsafe fn load_read_func(module: *const u16) -> apperr::Result<ReadConsoleInputExW> {
|
||||||
unsafe { get_module(module).and_then(|m| get_proc_address(m, c"ReadConsoleInputExW")) }
|
unsafe {
|
||||||
|
get_module(module)
|
||||||
|
.and_then(|m| get_proc_address(m, c"ReadConsoleInputExW".as_ptr()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `kernel32.dll` doesn't exist on OneCore variants of Windows.
|
// `kernel32.dll` doesn't exist on OneCore variants of Windows.
|
||||||
|
@ -564,21 +596,50 @@ unsafe fn load_library(name: *const u16) -> apperr::Result<NonNull<c_void>> {
|
||||||
/// of the function you're loading. No type checks whatsoever are performed.
|
/// of the function you're loading. No type checks whatsoever are performed.
|
||||||
//
|
//
|
||||||
// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
|
// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
|
||||||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apperr::Result<T> {
|
pub unsafe fn get_proc_address<T>(
|
||||||
|
handle: NonNull<c_void>,
|
||||||
|
name: *const c_char,
|
||||||
|
) -> apperr::Result<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name.as_ptr() as *const u8);
|
let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name as *const u8);
|
||||||
if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(get_last_error()) }
|
if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(get_last_error()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the "common" portion of ICU4C.
|
pub struct LibIcu {
|
||||||
pub fn load_libicuuc() -> apperr::Result<NonNull<c_void>> {
|
pub libicuuc: NonNull<c_void>,
|
||||||
unsafe { load_library(w!("icuuc.dll")) }
|
pub libicui18n: NonNull<c_void>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the internationalization portion of ICU4C.
|
pub fn load_icu() -> apperr::Result<LibIcu> {
|
||||||
pub fn load_libicui18n() -> apperr::Result<NonNull<c_void>> {
|
const fn const_ptr_u16_eq(a: *const u16, b: *const u16) -> bool {
|
||||||
unsafe { load_library(w!("icuin.dll")) }
|
unsafe {
|
||||||
|
let mut a = a;
|
||||||
|
let mut b = b;
|
||||||
|
loop {
|
||||||
|
if *a != *b {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if *a == 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
a = a.add(1);
|
||||||
|
b = b.add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIBICUUC: *const u16 = w_env!("EDIT_CFG_ICUUC_SONAME");
|
||||||
|
const LIBICUI18N: *const u16 = w_env!("EDIT_CFG_ICUI18N_SONAME");
|
||||||
|
|
||||||
|
if const { const_ptr_u16_eq(LIBICUUC, LIBICUI18N) } {
|
||||||
|
let icu = unsafe { load_library(LIBICUUC)? };
|
||||||
|
Ok(LibIcu { libicuuc: icu, libicui18n: icu })
|
||||||
|
} else {
|
||||||
|
let libicuuc = unsafe { load_library(LIBICUUC)? };
|
||||||
|
let libicui18n = unsafe { load_library(LIBICUI18N)? };
|
||||||
|
Ok(LibIcu { libicuuc, libicui18n })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of preferred languages for the current user.
|
/// Returns a list of preferred languages for the current user.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue