2025-10-07 13:06:16 +02:00
|
|
|
use fish_build_helper::{env_var, fish_build_dir, workspace_root};
|
Switch to builtin gettext implementation
This completely removes our runtime dependency on gettext. As a
replacement, we have our own code for runtime localization in
`src/wutil/gettext.rs`. It considers the relevant locale variables to
decide which message catalogs to take localizations from. The use of
locale variables is mostly the same as in gettext, with the notable
exception that we do not support "default dialects". If `LANGUAGE=ll` is
set and we don't have a `ll` catalog but a `ll_CC` catalog, we will use
the catalog with the country code suffix. If multiple such catalogs
exist, we use an arbitrary one. (At the moment we have at most one
catalog per language, so this is not particularly relevant.)
By using an `EnvStack` to pass variables to gettext at runtime, we now
respect locale variables which are not exported.
For early output, we don't have an `EnvStack` to pass, so we add an
initialization function which constructs an `EnvStack` containing the
relevant locale variables from the corresponding Environment variables.
Treat `LANGUAGE` as path variable. This add automatic colon-splitting.
The sourcing of catalogs is completely reworked. Instead of looking for
MO files at runtime, we create catalogs as Rust maps at build time, by
converting PO files into MO data, which is not stored, but immediately
parsed to extract the mappings. From the mappings, we create Rust source
code as a build artifact, which is then macro-included in the crate's
library, i.e. `crates/gettext-maps/src/lib.rs`. The code in
`src/wutil/gettext.rs` includes the message catalogs from this library,
resulting in the message catalogs being built into the executable.
The `localize-messages` feature can now be used to control whether to
build with gettext support. By default, it is enabled. If `msgfmt` is
not available at build time, and `gettext` is enabled, a warning will be
emitted and fish is built with gettext support, but without any message
catalogs, so localization will not work then.
As a performance optimization, for each language we cache a separate
Rust source file containing its catalog as a map. This allows us to
reuse parsing results if the corresponding PO files have not changed
since we cached the parsing result.
Note that this approach does not eliminate our build-time dependency on
gettext. The process for generating PO files (which uses `msguniq` and
`msgmerge`) is unchanged, and we still need `msgfmt` to translate from
PO to MO. We could parse PO files directly, but these are significantly
more complex to parse, so we use `msgfmt` to do it for us and parse the
resulting MO data.
Advantages of the new approach:
- We have no runtime dependency on gettext anymore.
- The implementation has the same behavior everywhere.
- Our implementation is significantly simpler than GNU gettext.
- We can have localization in cargo-only builds by embedding
localizations into the code.
Previously, localization in such builds could only work reliably as
long as the binary was not moved from the build directory.
- We no longer have to take care of building and installing MO files in
build systems; everything we need for localization to work happens
automatically when building fish.
- Reduced overhead when disabling localization, both in compilation time
and binary size.
Disadvantages of this approach:
- Our own runtime implementation of gettext needs to be maintained.
- The implementation has a more limited feature set (but I don't think
it lacks any features which have been in use by fish).
Part of #11726
Closes #11583
Closes #11725
Closes #11683
2025-08-22 20:03:45 +02:00
|
|
|
use rsconf::Target;
|
2023-08-18 06:57:56 +02:00
|
|
|
use std::env;
|
2025-06-22 12:19:47 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
2025-09-13 11:06:59 +02:00
|
|
|
fn canonicalize<P: AsRef<Path>>(path: P) -> PathBuf {
|
|
|
|
|
std::fs::canonicalize(path).unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-25 17:19:07 +02:00
|
|
|
fn main() {
|
2024-01-15 20:32:32 +01:00
|
|
|
setup_paths();
|
2023-08-18 06:57:56 +02:00
|
|
|
|
2024-01-12 12:50:58 +01:00
|
|
|
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
|
|
|
|
|
// language server.
|
2024-05-06 19:50:49 -05:00
|
|
|
|
2025-06-22 12:25:37 +02:00
|
|
|
rsconf::set_env_value(
|
2025-10-07 11:58:55 +02:00
|
|
|
"FISH_RESOLVED_BUILD_DIR",
|
Switch to builtin gettext implementation
This completely removes our runtime dependency on gettext. As a
replacement, we have our own code for runtime localization in
`src/wutil/gettext.rs`. It considers the relevant locale variables to
decide which message catalogs to take localizations from. The use of
locale variables is mostly the same as in gettext, with the notable
exception that we do not support "default dialects". If `LANGUAGE=ll` is
set and we don't have a `ll` catalog but a `ll_CC` catalog, we will use
the catalog with the country code suffix. If multiple such catalogs
exist, we use an arbitrary one. (At the moment we have at most one
catalog per language, so this is not particularly relevant.)
By using an `EnvStack` to pass variables to gettext at runtime, we now
respect locale variables which are not exported.
For early output, we don't have an `EnvStack` to pass, so we add an
initialization function which constructs an `EnvStack` containing the
relevant locale variables from the corresponding Environment variables.
Treat `LANGUAGE` as path variable. This add automatic colon-splitting.
The sourcing of catalogs is completely reworked. Instead of looking for
MO files at runtime, we create catalogs as Rust maps at build time, by
converting PO files into MO data, which is not stored, but immediately
parsed to extract the mappings. From the mappings, we create Rust source
code as a build artifact, which is then macro-included in the crate's
library, i.e. `crates/gettext-maps/src/lib.rs`. The code in
`src/wutil/gettext.rs` includes the message catalogs from this library,
resulting in the message catalogs being built into the executable.
The `localize-messages` feature can now be used to control whether to
build with gettext support. By default, it is enabled. If `msgfmt` is
not available at build time, and `gettext` is enabled, a warning will be
emitted and fish is built with gettext support, but without any message
catalogs, so localization will not work then.
As a performance optimization, for each language we cache a separate
Rust source file containing its catalog as a map. This allows us to
reuse parsing results if the corresponding PO files have not changed
since we cached the parsing result.
Note that this approach does not eliminate our build-time dependency on
gettext. The process for generating PO files (which uses `msguniq` and
`msgmerge`) is unchanged, and we still need `msgfmt` to translate from
PO to MO. We could parse PO files directly, but these are significantly
more complex to parse, so we use `msgfmt` to do it for us and parse the
resulting MO data.
Advantages of the new approach:
- We have no runtime dependency on gettext anymore.
- The implementation has the same behavior everywhere.
- Our implementation is significantly simpler than GNU gettext.
- We can have localization in cargo-only builds by embedding
localizations into the code.
Previously, localization in such builds could only work reliably as
long as the binary was not moved from the build directory.
- We no longer have to take care of building and installing MO files in
build systems; everything we need for localization to work happens
automatically when building fish.
- Reduced overhead when disabling localization, both in compilation time
and binary size.
Disadvantages of this approach:
- Our own runtime implementation of gettext needs to be maintained.
- The implementation has a more limited feature set (but I don't think
it lacks any features which have been in use by fish).
Part of #11726
Closes #11583
Closes #11725
Closes #11683
2025-08-22 20:03:45 +02:00
|
|
|
// If set by CMake, this might include symlinks. Since we want to compare this to the
|
|
|
|
|
// dir fish is executed in we need to canonicalize it.
|
|
|
|
|
canonicalize(fish_build_dir()).to_str().unwrap(),
|
2025-06-22 12:25:37 +02:00
|
|
|
);
|
|
|
|
|
|
2024-05-13 11:34:07 -05:00
|
|
|
// We need to canonicalize (i.e. realpath) the manifest dir because we want to be able to
|
|
|
|
|
// compare it directly as a string at runtime.
|
2025-08-20 15:19:20 +02:00
|
|
|
rsconf::set_env_value(
|
|
|
|
|
"CARGO_MANIFEST_DIR",
|
2025-09-13 12:00:47 +02:00
|
|
|
canonicalize(workspace_root()).to_str().unwrap(),
|
2025-08-20 15:19:20 +02:00
|
|
|
);
|
2024-01-03 20:02:32 +01:00
|
|
|
|
2024-12-29 13:37:28 +01:00
|
|
|
// Some build info
|
2025-10-07 13:06:16 +02:00
|
|
|
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env_var("TARGET").unwrap());
|
|
|
|
|
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap());
|
|
|
|
|
rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap());
|
2024-12-29 13:37:28 +01:00
|
|
|
|
2024-11-18 21:37:50 +01:00
|
|
|
let version = &get_version(&env::current_dir().unwrap());
|
2024-03-31 13:43:55 +08:00
|
|
|
// Per https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script,
|
|
|
|
|
// the source directory is the current working directory of the build script
|
2024-11-18 21:37:50 +01:00
|
|
|
rsconf::set_env_value("FISH_BUILD_VERSION", version);
|
|
|
|
|
|
2025-10-22 18:56:03 +02:00
|
|
|
// safety: single-threaded code.
|
|
|
|
|
unsafe { std::env::set_var("FISH_BUILD_VERSION", version) };
|
2024-03-01 21:49:49 +01:00
|
|
|
|
2025-02-12 17:01:54 +01:00
|
|
|
// These are necessary if built with embedded functions,
|
|
|
|
|
// but only in release builds (because rust-embed in debug builds reads from the filesystem).
|
2025-03-28 16:36:48 +01:00
|
|
|
#[cfg(feature = "embed-data")]
|
2025-09-16 10:05:26 +02:00
|
|
|
#[cfg(any(windows, not(debug_assertions)))]
|
2025-08-18 20:51:31 +02:00
|
|
|
rsconf::rebuild_if_path_changed("share");
|
2025-06-27 18:53:48 +02:00
|
|
|
|
2025-08-16 19:35:48 +02:00
|
|
|
#[cfg(feature = "gettext-extract")]
|
2025-05-28 01:42:01 +02:00
|
|
|
rsconf::rebuild_if_env_changed("FISH_GETTEXT_EXTRACTION_FILE");
|
|
|
|
|
|
Switch to builtin gettext implementation
This completely removes our runtime dependency on gettext. As a
replacement, we have our own code for runtime localization in
`src/wutil/gettext.rs`. It considers the relevant locale variables to
decide which message catalogs to take localizations from. The use of
locale variables is mostly the same as in gettext, with the notable
exception that we do not support "default dialects". If `LANGUAGE=ll` is
set and we don't have a `ll` catalog but a `ll_CC` catalog, we will use
the catalog with the country code suffix. If multiple such catalogs
exist, we use an arbitrary one. (At the moment we have at most one
catalog per language, so this is not particularly relevant.)
By using an `EnvStack` to pass variables to gettext at runtime, we now
respect locale variables which are not exported.
For early output, we don't have an `EnvStack` to pass, so we add an
initialization function which constructs an `EnvStack` containing the
relevant locale variables from the corresponding Environment variables.
Treat `LANGUAGE` as path variable. This add automatic colon-splitting.
The sourcing of catalogs is completely reworked. Instead of looking for
MO files at runtime, we create catalogs as Rust maps at build time, by
converting PO files into MO data, which is not stored, but immediately
parsed to extract the mappings. From the mappings, we create Rust source
code as a build artifact, which is then macro-included in the crate's
library, i.e. `crates/gettext-maps/src/lib.rs`. The code in
`src/wutil/gettext.rs` includes the message catalogs from this library,
resulting in the message catalogs being built into the executable.
The `localize-messages` feature can now be used to control whether to
build with gettext support. By default, it is enabled. If `msgfmt` is
not available at build time, and `gettext` is enabled, a warning will be
emitted and fish is built with gettext support, but without any message
catalogs, so localization will not work then.
As a performance optimization, for each language we cache a separate
Rust source file containing its catalog as a map. This allows us to
reuse parsing results if the corresponding PO files have not changed
since we cached the parsing result.
Note that this approach does not eliminate our build-time dependency on
gettext. The process for generating PO files (which uses `msguniq` and
`msgmerge`) is unchanged, and we still need `msgfmt` to translate from
PO to MO. We could parse PO files directly, but these are significantly
more complex to parse, so we use `msgfmt` to do it for us and parse the
resulting MO data.
Advantages of the new approach:
- We have no runtime dependency on gettext anymore.
- The implementation has the same behavior everywhere.
- Our implementation is significantly simpler than GNU gettext.
- We can have localization in cargo-only builds by embedding
localizations into the code.
Previously, localization in such builds could only work reliably as
long as the binary was not moved from the build directory.
- We no longer have to take care of building and installing MO files in
build systems; everything we need for localization to work happens
automatically when building fish.
- Reduced overhead when disabling localization, both in compilation time
and binary size.
Disadvantages of this approach:
- Our own runtime implementation of gettext needs to be maintained.
- The implementation has a more limited feature set (but I don't think
it lacks any features which have been in use by fish).
Part of #11726
Closes #11583
Closes #11725
Closes #11683
2025-08-22 20:03:45 +02:00
|
|
|
let build = cc::Build::new();
|
2024-01-19 16:02:31 -06:00
|
|
|
let mut target = Target::new_from(build).unwrap();
|
2023-05-16 13:02:22 -05:00
|
|
|
// Keep verbose mode on until we've ironed out rust build script stuff
|
2024-01-19 16:02:31 -06:00
|
|
|
target.set_verbose(true);
|
|
|
|
|
detect_cfgs(&mut target);
|
2024-08-30 18:30:03 +02:00
|
|
|
|
|
|
|
|
#[cfg(all(target_env = "gnu", target_feature = "crt-static"))]
|
2025-10-16 17:33:06 +02:00
|
|
|
compile_error!(
|
|
|
|
|
"Statically linking against glibc has unavoidable crashes and is unsupported. Use dynamic linking or link statically against musl."
|
|
|
|
|
);
|
2023-01-14 14:56:24 -08:00
|
|
|
}
|
2023-03-19 17:50:32 -05:00
|
|
|
|
2024-01-13 15:16:47 -06:00
|
|
|
/// Check target system support for certain functionality dynamically when the build is invoked,
|
|
|
|
|
/// without their having to be explicitly enabled in the `cargo build --features xxx` invocation.
|
|
|
|
|
///
|
|
|
|
|
/// We are using [`rsconf::enable_cfg()`] instead of [`rsconf::enable_feature()`] as rust features
|
|
|
|
|
/// should be used for things that a user can/would reasonably enable or disable to tweak or coerce
|
|
|
|
|
/// behavior, but here we are testing for whether or not things are supported altogether.
|
2023-03-19 17:50:32 -05:00
|
|
|
///
|
|
|
|
|
/// This can be used to enable features that we check for and conditionally compile according to in
|
|
|
|
|
/// our own codebase, but [can't be used to pull in dependencies](0) even if they're gated (in
|
|
|
|
|
/// `Cargo.toml`) behind a feature we just enabled.
|
|
|
|
|
///
|
|
|
|
|
/// [0]: https://github.com/rust-lang/cargo/issues/5499
|
2024-01-19 16:02:31 -06:00
|
|
|
fn detect_cfgs(target: &mut Target) {
|
2024-01-13 15:16:47 -06:00
|
|
|
for (name, handler) in [
|
2025-11-16 13:50:55 +01:00
|
|
|
// Ignore the first entry, it just sets up the type inference.
|
2025-09-28 12:52:12 +02:00
|
|
|
("", &(|_: &Target| false) as &dyn Fn(&Target) -> bool),
|
2024-04-05 21:42:50 +08:00
|
|
|
("apple", &detect_apple),
|
2023-03-19 18:11:09 -05:00
|
|
|
("bsd", &detect_bsd),
|
2025-04-01 17:22:11 +02:00
|
|
|
("cygwin", &detect_cygwin),
|
2025-11-16 13:50:55 +01:00
|
|
|
("have_eventfd", &|target| {
|
2024-08-15 17:26:25 +02:00
|
|
|
// FIXME: NetBSD 10 has eventfd, but the libc crate does not expose it.
|
2025-11-16 14:53:01 +01:00
|
|
|
if target_os() == "netbsd" {
|
2025-11-16 13:50:55 +01:00
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
target.has_header("sys/eventfd.h")
|
2024-08-15 17:26:25 +02:00
|
|
|
}
|
2024-01-13 16:04:05 -06:00
|
|
|
}),
|
2025-11-16 13:50:55 +01:00
|
|
|
("have_localeconv_l", &|target| {
|
|
|
|
|
target.has_symbol("localeconv_l")
|
|
|
|
|
}),
|
|
|
|
|
("have_pipe2", &|target| target.has_symbol("pipe2")),
|
|
|
|
|
("have_posix_spawn", &|target| target.has_header("spawn.h")),
|
|
|
|
|
("small_main_stack", &has_small_stack),
|
|
|
|
|
("use_prebuilt_docs", &|_| {
|
|
|
|
|
env_var("FISH_USE_PREBUILT_DOCS").is_some_and(|v| v == "TRUE")
|
|
|
|
|
}),
|
|
|
|
|
("using_cmake", &|_| {
|
|
|
|
|
option_env!("FISH_CMAKE_BINARY_DIR").is_some()
|
|
|
|
|
}),
|
|
|
|
|
("waitstatus_signal_ret", &|target| {
|
2025-09-28 12:52:12 +02:00
|
|
|
target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"])
|
2024-01-13 16:04:05 -06:00
|
|
|
}),
|
2023-03-19 18:11:09 -05:00
|
|
|
] {
|
2025-09-28 12:52:12 +02:00
|
|
|
rsconf::declare_cfg(name, handler(target))
|
2023-03-19 17:50:32 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 14:53:01 +01:00
|
|
|
// Target OS for compiling our crates, as opposed to the build script.
|
|
|
|
|
fn target_os() -> String {
|
|
|
|
|
env_var("CARGO_CFG_TARGET_OS").unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 12:52:12 +02:00
|
|
|
fn detect_apple(_: &Target) -> bool {
|
2025-11-16 14:53:01 +01:00
|
|
|
matches!(target_os().as_str(), "ios" | "macos")
|
2024-04-05 21:42:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-09-28 12:52:12 +02:00
|
|
|
fn detect_cygwin(_: &Target) -> bool {
|
2025-11-16 14:53:01 +01:00
|
|
|
target_os() == "cygwin"
|
2025-04-01 17:22:11 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-05 18:38:52 -05:00
|
|
|
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
|
2024-01-18 18:10:47 -06:00
|
|
|
/// `#[cfg(bsd)]`.
|
2023-03-19 17:50:32 -05:00
|
|
|
///
|
|
|
|
|
/// Rust offers fine-grained conditional compilation per-os for the popular operating systems, but
|
|
|
|
|
/// doesn't necessarily include less-popular forks nor does it group them into families more
|
|
|
|
|
/// specific than "windows" vs "unix" so we can conditionally compile code for BSD systems.
|
2025-09-28 12:52:12 +02:00
|
|
|
fn detect_bsd(_: &Target) -> bool {
|
2025-11-16 14:53:01 +01:00
|
|
|
let target_os = target_os();
|
|
|
|
|
let is_bsd = target_os.ends_with("bsd") || target_os == "dragonfly";
|
|
|
|
|
if matches!(
|
|
|
|
|
target_os.as_str(),
|
|
|
|
|
"dragonfly" | "freebsd" | "netbsd" | "openbsd"
|
|
|
|
|
) {
|
|
|
|
|
assert!(is_bsd, "Target incorrectly detected as not BSD!");
|
2023-05-05 18:38:52 -05:00
|
|
|
}
|
2025-09-28 12:52:12 +02:00
|
|
|
is_bsd
|
2023-03-19 17:50:32 -05:00
|
|
|
}
|
2023-05-16 13:02:22 -05:00
|
|
|
|
2024-05-30 10:59:52 -05:00
|
|
|
/// Rust sets the stack size of newly created threads to a sane value, but is at at the mercy of the
|
|
|
|
|
/// OS when it comes to the size of the main stack. Some platforms we support default to a tiny
|
|
|
|
|
/// 0.5 MiB main stack, which is insufficient for fish's MAX_EVAL_DEPTH/MAX_STACK_DEPTH values.
|
|
|
|
|
///
|
|
|
|
|
/// 0.5 MiB is small enough that we'd have to drastically reduce MAX_STACK_DEPTH to less than 10, so
|
|
|
|
|
/// we instead use a workaround to increase the main thread size.
|
2025-09-28 12:52:12 +02:00
|
|
|
fn has_small_stack(_: &Target) -> bool {
|
2024-04-05 21:42:50 +08:00
|
|
|
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "netbsd")))]
|
2025-09-28 12:52:12 +02:00
|
|
|
return false;
|
2024-05-30 10:59:52 -05:00
|
|
|
|
2024-08-15 18:29:06 +02:00
|
|
|
// NetBSD 10 also needs this but can't find pthread_get_stacksize_np.
|
|
|
|
|
#[cfg(target_os = "netbsd")]
|
2025-09-28 12:52:12 +02:00
|
|
|
return true;
|
2024-08-15 18:29:06 +02:00
|
|
|
|
2024-04-05 21:42:50 +08:00
|
|
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
2024-05-30 10:59:52 -05:00
|
|
|
{
|
|
|
|
|
use core::ffi;
|
|
|
|
|
|
2025-10-22 18:56:03 +02:00
|
|
|
unsafe extern "C" {
|
|
|
|
|
unsafe fn pthread_get_stacksize_np(thread: *const ffi::c_void) -> usize;
|
|
|
|
|
unsafe fn pthread_self() -> *const ffi::c_void;
|
2024-05-30 10:59:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// build.rs is executed on the main thread, so we are getting the main thread's stack size.
|
|
|
|
|
// Modern macOS versions default to an 8 MiB main stack but legacy OS X have a 0.5 MiB one.
|
|
|
|
|
let stack_size = unsafe { pthread_get_stacksize_np(pthread_self()) };
|
2024-05-30 12:25:06 -05:00
|
|
|
const TWO_MIB: usize = 2 * 1024 * 1024 - 1;
|
2025-10-04 19:25:10 -07:00
|
|
|
stack_size <= TWO_MIB
|
2024-05-30 10:59:52 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 15:31:00 -06:00
|
|
|
fn setup_paths() {
|
2025-03-08 00:52:06 +08:00
|
|
|
#[cfg(windows)]
|
|
|
|
|
use unix_path::{Path, PathBuf};
|
|
|
|
|
|
2025-10-26 23:08:22 +01:00
|
|
|
fn overridable_path(
|
|
|
|
|
env_var_name: &str,
|
|
|
|
|
f: impl FnOnce(Option<String>) -> Option<PathBuf>,
|
|
|
|
|
) -> Option<PathBuf> {
|
2025-10-07 12:03:52 +02:00
|
|
|
rsconf::rebuild_if_env_changed(env_var_name);
|
2025-10-26 23:08:22 +01:00
|
|
|
let maybe_path = f(env_var(env_var_name));
|
|
|
|
|
if let Some(path) = maybe_path.as_ref() {
|
|
|
|
|
rsconf::set_env_value(env_var_name, path.to_str().unwrap());
|
|
|
|
|
}
|
|
|
|
|
maybe_path
|
2024-01-15 20:32:32 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-07 12:03:52 +02:00
|
|
|
fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
|
|
|
|
|
let path = PathBuf::from(path);
|
|
|
|
|
if path.is_relative() {
|
|
|
|
|
parent_if_relative.join(path)
|
|
|
|
|
} else {
|
|
|
|
|
path
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-15 20:32:32 +01:00
|
|
|
|
2025-10-07 12:03:52 +02:00
|
|
|
let prefix = overridable_path("PREFIX", |env_prefix| {
|
2025-10-26 23:08:22 +01:00
|
|
|
Some(PathBuf::from(
|
|
|
|
|
env_prefix.unwrap_or("/usr/local".to_string()),
|
|
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
2024-01-19 15:31:00 -06:00
|
|
|
|
2025-10-07 12:03:52 +02:00
|
|
|
overridable_path("SYSCONFDIR", |env_sysconfdir| {
|
2025-10-26 23:08:22 +01:00
|
|
|
Some(join_if_relative(
|
2025-10-27 11:20:26 +01:00
|
|
|
&prefix,
|
2025-10-07 12:03:52 +02:00
|
|
|
env_sysconfdir.unwrap_or(
|
2025-10-27 11:20:26 +01:00
|
|
|
// Embedded builds use "/etc," not "$PREFIX/etc".
|
2025-10-07 12:03:52 +02:00
|
|
|
if cfg!(feature = "embed-data") {
|
|
|
|
|
"/etc/"
|
|
|
|
|
} else {
|
|
|
|
|
"etc/"
|
|
|
|
|
}
|
|
|
|
|
.to_string(),
|
|
|
|
|
),
|
2025-10-26 23:08:22 +01:00
|
|
|
))
|
2025-10-07 12:03:52 +02:00
|
|
|
});
|
2024-01-19 15:31:00 -06:00
|
|
|
|
2025-10-26 23:08:22 +01:00
|
|
|
let default_ok = !cfg!(feature = "embed-data");
|
|
|
|
|
let datadir = overridable_path("DATADIR", |env_datadir| {
|
|
|
|
|
let default = default_ok.then_some("share/".to_string());
|
|
|
|
|
env_datadir
|
|
|
|
|
.or(default)
|
|
|
|
|
.map(|p| join_if_relative(&prefix, p))
|
|
|
|
|
});
|
|
|
|
|
overridable_path("BINDIR", |env_bindir| {
|
|
|
|
|
let default = default_ok.then_some("bin/".to_string());
|
|
|
|
|
env_bindir.or(default).map(|p| join_if_relative(&prefix, p))
|
|
|
|
|
});
|
|
|
|
|
overridable_path("DOCDIR", |env_docdir| {
|
|
|
|
|
let default = default_ok.then_some("doc/fish".to_string());
|
|
|
|
|
env_docdir.or(default).map(|p| {
|
|
|
|
|
join_if_relative(
|
|
|
|
|
&datadir
|
|
|
|
|
.expect("Setting DOCDIR without setting DATADIR is not currently supported"),
|
|
|
|
|
p,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
});
|
2024-01-15 20:32:32 +01:00
|
|
|
}
|
2024-03-01 21:49:49 +01:00
|
|
|
|
2024-03-31 13:43:55 +08:00
|
|
|
fn get_version(src_dir: &Path) -> String {
|
2024-03-01 21:49:49 +01:00
|
|
|
use std::fs::read_to_string;
|
|
|
|
|
use std::process::Command;
|
|
|
|
|
|
2025-10-07 13:06:16 +02:00
|
|
|
if let Some(var) = env_var("FISH_BUILD_VERSION") {
|
2024-03-01 21:49:49 +01:00
|
|
|
return var;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-22 02:34:27 +08:00
|
|
|
let path = src_dir.join("version");
|
2024-03-01 21:49:49 +01:00
|
|
|
if let Ok(strver) = read_to_string(path) {
|
2025-06-28 09:26:14 +02:00
|
|
|
return strver;
|
2024-03-01 21:49:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let args = &["describe", "--always", "--dirty=-dirty"];
|
|
|
|
|
if let Ok(output) = Command::new("git").args(args).output() {
|
|
|
|
|
let rev = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
|
|
|
if !rev.is_empty() {
|
2024-06-10 19:27:53 +02:00
|
|
|
// If it contains a ".", we have a proper version like "3.7",
|
|
|
|
|
// or "23.2.1-1234-gfab1234"
|
2024-12-14 09:25:35 +01:00
|
|
|
if rev.contains('.') {
|
2024-06-10 19:27:53 +02:00
|
|
|
return rev;
|
|
|
|
|
}
|
|
|
|
|
// If it doesn't, we probably got *just* the commit SHA,
|
|
|
|
|
// like "f1242abcdef".
|
|
|
|
|
// So we prepend the crate version so it at least looks like
|
|
|
|
|
// "3.8-gf1242abcdef"
|
|
|
|
|
// This lacks the commit *distance*, but that can't be helped without
|
|
|
|
|
// tags.
|
|
|
|
|
let version = env!("CARGO_PKG_VERSION").to_owned();
|
|
|
|
|
return version + "-g" + &rev;
|
2024-03-01 21:49:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-09 16:57:01 +01:00
|
|
|
// git did not tell us a SHA either because it isn't installed,
|
|
|
|
|
// or because it refused (safe.directory applies to `git describe`!)
|
|
|
|
|
// So we read the SHA ourselves.
|
|
|
|
|
fn get_git_hash() -> Result<String, Box<dyn std::error::Error>> {
|
2025-09-13 12:00:47 +02:00
|
|
|
let workspace_root = workspace_root();
|
2025-09-13 10:18:32 +02:00
|
|
|
let gitdir = workspace_root.join(".git");
|
|
|
|
|
let jjdir = workspace_root.join(".jj");
|
2024-12-23 15:03:40 +01:00
|
|
|
let commit_id = if gitdir.exists() {
|
|
|
|
|
// .git/HEAD contains ref: refs/heads/branch
|
|
|
|
|
let headpath = gitdir.join("HEAD");
|
|
|
|
|
let headstr = read_to_string(headpath)?;
|
2025-03-04 12:53:02 +08:00
|
|
|
let headref = headstr.split(' ').nth(1).unwrap().trim();
|
2024-12-23 15:03:40 +01:00
|
|
|
|
|
|
|
|
// .git/refs/heads/branch contains the SHA
|
|
|
|
|
let refpath = gitdir.join(headref);
|
|
|
|
|
// Shorten to 9 characters (what git describe does currently)
|
|
|
|
|
read_to_string(refpath)?
|
|
|
|
|
} else if jjdir.exists() {
|
|
|
|
|
let output = Command::new("jj")
|
|
|
|
|
.args([
|
|
|
|
|
"log",
|
|
|
|
|
"--revisions",
|
|
|
|
|
"@",
|
|
|
|
|
"--no-graph",
|
|
|
|
|
"--ignore-working-copy",
|
|
|
|
|
"--template",
|
|
|
|
|
"commit_id",
|
|
|
|
|
])
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
String::from_utf8_lossy(&output.stdout).to_string()
|
|
|
|
|
} else {
|
|
|
|
|
return Err("did not find either of .git or .jj".into());
|
|
|
|
|
};
|
|
|
|
|
let refstr = &commit_id[0..9];
|
2024-12-09 16:57:01 +01:00
|
|
|
let refstr = refstr.trim();
|
|
|
|
|
|
|
|
|
|
let version = env!("CARGO_PKG_VERSION").to_owned();
|
|
|
|
|
Ok(version + "-g" + refstr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_git_hash().expect("Could not get a version. Either set $FISH_BUILD_VERSION or install git.")
|
2024-03-01 21:49:49 +01:00
|
|
|
}
|