Skip to content

Commit a36f3d6

Browse files
committed
Make the ICU SONAME configurable
1 parent 91a9a5f commit a36f3d6

File tree

7 files changed

+246
-80
lines changed

7 files changed

+246
-80
lines changed

README.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,48 @@ You can install the latest version with WinGet:
1919
winget install Microsoft.Edit
2020
```
2121

22-
### Notes to Package Maintainers
23-
24-
The canonical executable name is "edit" and the alternative name is "msedit".
25-
26-
We're aware of the potential conflict of "edit" with existing commands and as such recommend naming packages and executables "msedit".
27-
Names such as "ms-edit" should be avoided.
28-
Assigning an "edit" alias is recommended if possible.
29-
3022
## Build Instructions
3123

3224
* [Install Rust](https://www.rust-lang.org/tools/install)
3325
* Install the nightly toolchain: `rustup install nightly`
3426
* Alternatively, set the environment variable `RUSTC_BOOTSTRAP=1`
3527
* Clone the repository
3628
* For a release build, run: `cargo build --config .cargo/release.toml --release`
29+
30+
## Notes to Package Maintainers
31+
32+
### Package Naming
33+
34+
The canonical executable name is "edit" and the alternative name is "msedit".
35+
We're aware of the potential conflict of "edit" with existing commands and recommend alternatively naming packages and executables "msedit".
36+
Names such as "ms-edit" should be avoided.
37+
Assigning an "edit" alias is recommended, if possible.
38+
39+
### ICU library name (SONAME)
40+
41+
This project _optionally_ depends on the ICU library for its Search and Replace functionality.
42+
By default, the project will look for a SONAME without version suffix:
43+
* Windows: `icuuc.dll`
44+
* macOS: `libicuuc.dylib`
45+
* UNIX, and other OS: `libicuuc.so`
46+
47+
If your installation uses a different SONAME, please set the following environment variable at build time:
48+
* `EDIT_CFG_ICUUC_SONAME`:
49+
Set this to `libicuuc.so.76`, for instance.
50+
* `EDIT_CFG_ICUI18N_SONAME`:
51+
Set this to `libicui18n.so.76`, for instance.
52+
53+
Additionally, this project assumes that the ICU exports are without version suffix, such as `u_errorName`.
54+
If your installation uses versioned exports, please set:
55+
* `EDIT_CFG_ICU_RENAMING_VERSION`:
56+
If the ICU library exports have a version suffix, such as `u_errorName_76`, set this to the version number, such as `76`.
57+
58+
Finally, you can set the following environment variables:
59+
* `EDIT_CFG_ICU_RENAMING_AUTO_DETECT`:
60+
If set to `true`, the executable will try to detect the `EDIT_CFG_ICU_RENAMING_VERSION` value at runtime.
61+
The way it does this is not officially supported by ICU and as such is not recommended to be relied upon.
62+
63+
To test your settings, run `cargo test` again but with the `--ignored` flag. For instance:
64+
```sh
65+
cargo test -- --ignored
66+
```

build.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,68 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use core::panic;
5+
use std::env::VarError;
6+
7+
#[derive(PartialEq, Eq)]
8+
enum TargetOs {
9+
Windows,
10+
MacOS,
11+
Unix,
12+
}
13+
414
fn main() {
15+
let target_os = match env_fallback("CARGO_CFG_TARGET_OS", "").as_str() {
16+
"windows" => TargetOs::Windows,
17+
"macos" | "ios" => TargetOs::MacOS,
18+
_ => TargetOs::Unix,
19+
};
20+
let icuuc_soname = env_fallback(
21+
"EDIT_CFG_ICUUC_SONAME",
22+
match target_os {
23+
TargetOs::Windows => "icuuc.dll",
24+
TargetOs::MacOS => "libicuuc.dylib",
25+
TargetOs::Unix => "libicuuc.so",
26+
},
27+
);
28+
let icui18n_soname = env_fallback(
29+
"EDIT_CFG_ICUI18N_SONAME",
30+
match target_os {
31+
TargetOs::Windows => "icuin.dll",
32+
TargetOs::MacOS => "libicui18n.dylib",
33+
TargetOs::Unix => "libicui18n.so",
34+
},
35+
);
36+
let renaming_auto_detect =
37+
env_fallback("EDIT_CFG_ICU_RENAMING_AUTO_DETECT", "false").parse::<bool>().unwrap();
38+
let renaming_version = {
39+
let mut v = env_fallback("EDIT_CFG_ICU_RENAMING_VERSION", "");
40+
if v.parse::<i32>().unwrap_or(0) > 0 {
41+
v.insert(0, '_');
42+
}
43+
v
44+
};
45+
46+
if renaming_auto_detect && !renaming_version.is_empty() {
47+
panic!(
48+
"Either `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` or `EDIT_CFG_ICU_RENAMING_VERSION` must be set, but not both"
49+
);
50+
}
51+
52+
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUUC_SONAME");
53+
println!("cargo::rustc-env=EDIT_CFG_ICUUC_SONAME={icuuc_soname}");
54+
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUI18N_SONAME");
55+
println!("cargo::rustc-env=EDIT_CFG_ICUI18N_SONAME={icui18n_soname}");
56+
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_RENAMING_VERSION");
57+
println!("cargo::rustc-env=EDIT_CFG_ICU_RENAMING_VERSION={renaming_version}");
58+
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_RENAMING_AUTO_DETECT");
59+
println!("cargo::rustc-check-cfg=cfg(edit_icu_renaming_auto_detect)");
60+
if renaming_auto_detect {
61+
println!("cargo::rustc-cfg=edit_icu_renaming_auto_detect");
62+
}
63+
564
#[cfg(windows)]
6-
if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
65+
if target_os == TargetOs::Windows {
766
winresource::WindowsResource::new()
867
.set_manifest_file("src/bin/edit/edit.exe.manifest")
968
.set("FileDescription", "Microsoft Edit")
@@ -13,3 +72,13 @@ fn main() {
1372
.unwrap();
1473
}
1574
}
75+
76+
fn env_fallback(name: &str, fallback: &str) -> String {
77+
match std::env::var(name) {
78+
Ok(value) => value,
79+
Err(VarError::NotPresent) => fallback.to_string(),
80+
Err(VarError::NotUnicode(_)) => {
81+
panic!("Environment variable `{name}` is not valid Unicode")
82+
}
83+
}
84+
}

src/arena/release.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ impl Arena {
7979
})
8080
}
8181

82+
pub fn is_empty(&self) -> bool {
83+
self.base == NonNull::dangling()
84+
}
85+
8286
pub fn offset(&self) -> usize {
8387
self.offset.get()
8488
}
@@ -171,7 +175,7 @@ impl Arena {
171175

172176
impl Drop for Arena {
173177
fn drop(&mut self) {
174-
if self.base != NonNull::dangling() {
178+
if !self.is_empty() {
175179
unsafe { sys::virtual_release(self.base, self.capacity) };
176180
}
177181
}

src/arena/scratch.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ pub fn init(capacity: usize) -> apperr::Result<()> {
4646
/// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`.
4747
pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> {
4848
unsafe {
49+
#[cfg(test)]
50+
if S_SCRATCH[0].is_empty() {
51+
init(128 * 1024 * 1024).unwrap();
52+
}
53+
4954
#[cfg(debug_assertions)]
5055
let conflict = conflict.map(|a| a.delegate_target_unchecked());
5156

src/icu.rs

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! Bindings to the ICU library.
55
66
use std::cmp::Ordering;
7-
use std::ffi::CStr;
7+
use std::ffi::{CStr, c_char};
88
use std::mem;
99
use std::mem::MaybeUninit;
1010
use std::ops::Range;
@@ -904,32 +904,38 @@ struct LibraryFunctions {
904904
uregex_end64: icu_ffi::uregex_end64,
905905
}
906906

907+
macro_rules! proc_name {
908+
($s:literal) => {
909+
concat!($s, env!("EDIT_CFG_ICU_RENAMING_VERSION"), "\0").as_ptr() as *const c_char
910+
};
911+
}
912+
907913
// Found in libicuuc.so on UNIX, icuuc.dll/icu.dll on Windows.
908-
const LIBICUUC_PROC_NAMES: [&CStr; 10] = [
909-
c"u_errorName",
910-
c"ucasemap_open",
911-
c"ucasemap_utf8FoldCase",
912-
c"ucnv_getAvailableName",
913-
c"ucnv_getStandardName",
914-
c"ucnv_open",
915-
c"ucnv_close",
916-
c"ucnv_convertEx",
917-
c"utext_setup",
918-
c"utext_close",
914+
const LIBICUUC_PROC_NAMES: [*const c_char; 10] = [
915+
proc_name!("u_errorName"),
916+
proc_name!("ucasemap_open"),
917+
proc_name!("ucasemap_utf8FoldCase"),
918+
proc_name!("ucnv_getAvailableName"),
919+
proc_name!("ucnv_getStandardName"),
920+
proc_name!("ucnv_open"),
921+
proc_name!("ucnv_close"),
922+
proc_name!("ucnv_convertEx"),
923+
proc_name!("utext_setup"),
924+
proc_name!("utext_close"),
919925
];
920926

921927
// Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows.
922-
const LIBICUI18N_PROC_NAMES: [&CStr; 10] = [
923-
c"ucol_open",
924-
c"ucol_strcollUTF8",
925-
c"uregex_open",
926-
c"uregex_close",
927-
c"uregex_setTimeLimit",
928-
c"uregex_setUText",
929-
c"uregex_reset64",
930-
c"uregex_findNext",
931-
c"uregex_start64",
932-
c"uregex_end64",
928+
const LIBICUI18N_PROC_NAMES: [*const c_char; 10] = [
929+
proc_name!("ucol_open"),
930+
proc_name!("ucol_strcollUTF8"),
931+
proc_name!("uregex_open"),
932+
proc_name!("uregex_close"),
933+
proc_name!("uregex_setTimeLimit"),
934+
proc_name!("uregex_setUText"),
935+
proc_name!("uregex_reset64"),
936+
proc_name!("uregex_findNext"),
937+
proc_name!("uregex_start64"),
938+
proc_name!("uregex_end64"),
933939
];
934940

935941
enum LibraryFunctionsState {
@@ -952,10 +958,7 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
952958
unsafe {
953959
LIBRARY_FUNCTIONS = LibraryFunctionsState::Failed;
954960

955-
let Ok(libicuuc) = sys::load_libicuuc() else {
956-
return;
957-
};
958-
let Ok(libicui18n) = sys::load_libicui18n() else {
961+
let Ok(icu) = sys::load_icu() else {
959962
return;
960963
};
961964

@@ -979,25 +982,26 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
979982
let mut funcs = MaybeUninit::<LibraryFunctions>::uninit();
980983
let mut ptr = funcs.as_mut_ptr() as *mut TransparentFunction;
981984

982-
#[cfg(unix)]
985+
#[cfg(edit_icu_renaming_auto_detect)]
983986
let scratch_outer = scratch_arena(None);
984-
#[cfg(unix)]
985-
let suffix = sys::icu_proc_suffix(&scratch_outer, libicuuc);
986-
987-
for (handle, names) in
988-
[(libicuuc, &LIBICUUC_PROC_NAMES[..]), (libicui18n, &LIBICUI18N_PROC_NAMES[..])]
989-
{
990-
for name in names {
991-
#[cfg(unix)]
987+
#[cfg(edit_icu_renaming_auto_detect)]
988+
let suffix = sys::icu_detect_renaming_suffix(&scratch_outer, icu.libicuuc);
989+
990+
for (handle, names) in [
991+
(icu.libicuuc, &LIBICUUC_PROC_NAMES[..]),
992+
(icu.libicui18n, &LIBICUI18N_PROC_NAMES[..]),
993+
] {
994+
for &name in names {
995+
#[cfg(edit_icu_renaming_auto_detect)]
992996
let scratch = scratch_arena(Some(&scratch_outer));
993-
#[cfg(unix)]
994-
let name = &sys::add_icu_proc_suffix(&scratch, name, &suffix);
997+
#[cfg(edit_icu_renaming_auto_detect)]
998+
let name = sys::icu_add_renaming_suffix(&scratch, name, &suffix);
995999

9961000
let Ok(func) = sys::get_proc_address(handle, name) else {
9971001
debug_assert!(
9981002
false,
999-
"Failed to load ICU function: {}",
1000-
name.to_string_lossy()
1003+
"Failed to load ICU function: {:?}",
1004+
CStr::from_ptr(name)
10011005
);
10021006
return;
10031007
};
@@ -1293,6 +1297,12 @@ mod icu_ffi {
12931297
mod tests {
12941298
use super::*;
12951299

1300+
#[ignore]
1301+
#[test]
1302+
fn init() {
1303+
assert!(init_if_needed().is_ok());
1304+
}
1305+
12961306
#[test]
12971307
fn test_compare_strings_ascii() {
12981308
// Empty strings

0 commit comments

Comments
 (0)