Compare commits

..

1 Commits

Author SHA1 Message Date
Johannes Altmanninger
233355bc9b __fish_apropos: assume only /usr/bin/apropos is broken
Also, remove a macOS version check that's doom to obsolescence,
and extract a function (WIP).
2025-11-15 14:33:55 +01:00
786 changed files with 37024 additions and 146573 deletions

View File

@@ -1,2 +1,8 @@
[alias]
xtask = "run --package xtask --"
# This file is _not_ included in the tarballs for now
# Binary builds on Linux packaging infrastructure need to overwrite it to make `cargo vendor` work
# Releases and development builds made using OBS/Launchpad will _not_ reflect the contents of this
# file
[resolver]
# Make cargo 1.84+ respect MSRV (rust-version in Cargo.toml)
incompatible-rust-versions = "fallback"

48
.cirrus.yml Normal file
View File

@@ -0,0 +1,48 @@
env:
CIRRUS_CLONE_DEPTH: 100
CI: 1
linux_task:
matrix:
- name: alpine
container: &step
image: ghcr.io/krobelus/fish-ci/alpine:latest
memory: 4GB
- name: ubuntu-oldest-supported
container:
<<: *step
image: ghcr.io/krobelus/fish-ci/ubuntu-oldest-supported:latest
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- mkdir build && cd build
- FISH_TEST_MAX_CONCURRENCY=6 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- ninja -j 6 fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
freebsd_task:
matrix:
- name: FreeBSD Stable
freebsd_instance:
image: freebsd-14-3-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
tests_script:
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
# libclang.so is a required build dependency for rust-c++ ffi bridge
- pkg install -y llvm
# BSDs have the following behavior: root may open or access files even if
# the mode bits would otherwise disallow it. For example root may open()
# a file with write privileges even if the file has mode 400. This breaks
# our tests for e.g. cd and path. So create a new unprivileged user to run tests.
- pw user add -n fish-user -s /bin/csh -d /home/fish-user
- mkdir -p /home/fish-user
- chown -R fish-user /home/fish-user
- mkdir build && cd build
- chown -R fish-user ..
- sudo -u fish-user -s whoami
- sudo -u fish-user -s FISH_TEST_MAX_CONCURRENCY=1 cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
- sudo -u fish-user -s ninja -j 6 fish
- sudo -u fish-user -s ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'

View File

@@ -2,12 +2,7 @@
#
# 1) lines can be up to 100 chars long rather than 80, and
# 2) use a four space indent rather than two spaces.
# 3) for C files, put * on the right.
#
---
Language: C
BasedOnStyle: LLVM
BasedOnStyle: Google
ColumnLimit: 100
IndentWidth: 4
PointerAlignment: Right
---

View File

@@ -9,11 +9,12 @@ trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
[{Makefile,{BSD,GNU}makefile}]
[{Makefile,*.in}]
indent_style = tab
[*.{md,rst}]
max_line_length = unset
trim_trailing_whitespace = false
[*.sh]
indent_size = 4
@@ -21,7 +22,7 @@ indent_size = 4
[build_tools/release.sh]
max_line_length = 72
[Vagrantfile]
[Dockerfile]
indent_size = 2
[share/{completions,functions}/**.fish]
@@ -29,6 +30,3 @@ max_line_length = unset
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
max_line_length = 72
[*.{toml,yml}]
indent_size = 2

5
.gitattributes vendored
View File

@@ -14,10 +14,15 @@
.gitattributes export-ignore
.gitignore export-ignore
/build_tools/make_tarball.sh export-ignore
/debian export-ignore
/debian/* export-ignore
/.github export-ignore
/.github/* export-ignore
/.builds export-ignore
/.builds/* export-ignore
# to make cargo vendor work correctly
/.cargo/ export-ignore
/.cargo/config.toml export-ignore
# for linguist, which drives GitHub's language statistics
alpine.js linguist-vendored

View File

@@ -1,8 +1,11 @@
## Description
Talk about your changes here.
Fixes issue #
## TODOs:
<!-- Check off what what has been done so far. -->
- [ ] If addressing an issue, a commit message mentions `Fixes issue #<issue-number>`
<!-- Just check off what what we know been done so far. We can help you with this stuff. -->
- [ ] Changes to fish usage are reflected in user documentation/manpages.
- [ ] Tests have been added for regressions fixed
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Usually skipped for changes to completions -->
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Don't document changes for completions inside CHANGELOG.rst, there are lot of such edits -->

View File

@@ -30,7 +30,9 @@ runs:
sudo apt install \
diffutils $(: "for diff") \
git \
gettext \
less \
$(if ${{ inputs.include_pcre }}; then echo libpcre2-dev; fi) \
python3-pexpect \
tmux \
wget \

View File

@@ -10,11 +10,12 @@ runs:
run: |
set -x
sudo pip install uv --break-system-packages
command -v uv
# Check that pyproject.toml and the lock file are in sync.
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
uv venv ~/.local --allow-existing
uv pip install --group=dev
# TODO Use "uv" to install Python as well.
: 'Note that --no-managed-python below would be implied but be explicit'
uv='env UV_PYTHON=python uv --no-managed-python'
$uv lock --check
# Install globally.
sudo $uv pip install --group=dev --system --break-system-packages
# Smoke test.
python -c 'import sphinx; import sphinx_markdown_builder'
python3 -c 'import sphinx; import sphinx_markdown_builder'

View File

@@ -25,7 +25,7 @@ runs:
set -x
toolchain=$(
case "$toolchain_channel" in
(stable) echo 1.96 ;; # updatecli.d/rust.yml
(stable) echo 1.91 ;; # updatecli.d/rust.yml
(msrv) echo 1.85 ;; # updatecli.d/rust.yml
(*)
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Set label and milestone
id: set-label-milestone
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0, build_tools/update-dependencies.sh
uses: actions/github-script@v7
with:
script: |
const completionsLabel = 'completions';

View File

@@ -0,0 +1,64 @@
name: Build Docker test images
on:
push:
branches:
- master
paths:
- 'docker/**'
workflow_dispatch:
concurrency:
group: docker-builds
env:
REGISTRY: ghcr.io
NAMESPACE: fish-ci
jobs:
docker-build:
if: github.repository_owner == 'fish-shell'
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: alpine
- os: ubuntu-latest
target: ubuntu-oldest-supported
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Login to Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
flavor: |
latest=true
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: docker/context
push: true
file: docker/${{ matrix.target }}.Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,32 +0,0 @@
name: Linux development builds
on:
push:
branches:
- buildscript
jobs:
deploy:
runs-on: ubuntu-latest
environment: linux-development
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: astral-sh/setup-uv@v7
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: sudo apt install debhelper devscripts dpkg-dev
- name: Create tarball and source packages
run: |
version=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
mkdir /tmp/gpg
echo "$SIGNING_GPG_KEY" > /tmp/gpg/signing-gpg-key
mkdir /tmp/fish-built
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
FISH_ARTEFACT_PATH=/tmp/fish-built DEB_SIGN_KEYFILE=/tmp/gpg/signing-gpg-key ./build_tools/make_linux_packages.sh $version
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
with:
name: linux-source-packages
path: |
/tmp/fish-built
! /tmp/fish-built/fish-*/* # don't include the unpacked source directory

View File

@@ -1,24 +0,0 @@
name: Lint Dependencies
on:
push:
paths:
- '.github/workflows/lint-dependencies.yml'
- 'Cargo.lock'
- '**/Cargo.toml'
- 'deny.toml'
pull_request:
paths:
- '.github/workflows/lint-dependencies.yml'
- 'Cargo.lock'
- '**/Cargo.toml'
- 'deny.toml'
jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: EmbarkStudios/cargo-deny-action@8a45e1c7c9a95dfae3276e89a553705e40ae45a2 # v2.0.20, build_tools/update-dependencies.sh
with:
command: check licenses
arguments: --all-features --locked --exclude-dev
rust-version: 1.96 # updatecli.d/rust.yml

View File

@@ -9,40 +9,19 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@stable
with:
components: rustfmt
- name: install dependencies
run: pip install ruff
- name: build fish
run: cargo build --bin fish_indent
run: cargo build
- name: check format
run: PATH="target/debug:$PATH" cargo xtask format --all --check
run: PATH="target/debug:$PATH" build_tools/style.fish --all --check
- name: check rustfmt
run: find build.rs crates src -type f -name '*.rs' | xargs rustfmt --check
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
- name: Update package database
run: sudo apt-get update
- name: Install shellcheck
run: sudo apt install shellcheck
- name: shellcheck
run: cargo xtask shellcheck
po_files_up_to_date:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: ./.github/actions/rust-toolchain@stable
- name: Install deps
uses: ./.github/actions/install-dependencies
- name: Check PO files
run: cargo xtask gettext check
clippy:
runs-on: ubuntu-latest
@@ -53,36 +32,25 @@ jobs:
features: ""
- rust_version: "stable"
features: "--no-default-features"
- rust_version: "stable"
features: "--all-features"
- rust_version: "msrv"
features: ""
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: ${{ matrix.rust_version }}
components: clippy
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: |
sudo apt install gettext
- name: Patch Cargo.toml to deny unknown lints
run: |
if [ "${{ matrix.rust_version }}" = stable ]; then
sed -i /^rust.unknown_lints/d Cargo.toml
fi
- name: cargo clippy
run: cargo clippy --workspace --all-targets ${{ matrix.features }} -- --deny=warnings
rustdoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@stable
- name: Update package database
run: sudo apt-get update
- name: Install deps
run: |
sudo apt install gettext

View File

@@ -18,7 +18,7 @@ jobs:
pull-requests: write # for dessant/lock-threads to lock PRs
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@b2726a6ae6f1e1b06eb0ff28f7e4fb5e4246bbca # v6.0.2, build_tools/update-dependencies.sh
- uses: dessant/lock-threads@v4
with:
github-token: ${{ github.token }}
issue-inactive-days: '365'

View File

@@ -16,7 +16,7 @@ jobs:
name: Pre-release checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
@@ -36,12 +36,10 @@ jobs:
version: ${{ steps.version.outputs.version }}
tarball-name: ${{ steps.version.outputs.tarball-name }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Update package database
run: sudo apt-get update
- name: Install dependencies
run: sudo apt install cmake gettext ninja-build python3-pip
- uses: ./.github/actions/install-sphinx
@@ -63,7 +61,7 @@ jobs:
sed -n 2p "$relnotes" | grep -q '^$'
sed -i 1,2d "$relnotes"
- name: Upload tarball artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
uses: actions/upload-artifact@v4
with:
name: source-tarball
path: |
@@ -76,7 +74,7 @@ jobs:
name: Build single-file fish for Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
@@ -84,8 +82,6 @@ jobs:
uses: ./.github/actions/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl
- name: Update package database
run: sudo apt-get update
- name: Install dependencies
run: sudo apt install crossbuild-essential-arm64 gettext musl-tools
- uses: ./.github/actions/install-sphinx
@@ -104,7 +100,7 @@ jobs:
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
-C target/$arch-unknown-linux-musl/release fish
done
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, build_tools/update-dependencies.sh
- uses: actions/upload-artifact@v4
with:
name: Static builds for Linux
path: fish-${{ inputs.version }}-linux-*.tar.xz
@@ -118,19 +114,19 @@ jobs:
name: Create release draft
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1, build_tools/update-dependencies.sh
uses: actions/download-artifact@v4
with:
merge-multiple: true
path: /tmp/artifacts
- name: List artifacts
run: find /tmp/artifacts -type f
- name: Create draft release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0, build_tools/update-dependencies.sh
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.version }}
name: fish ${{ inputs.version }}
@@ -146,7 +142,7 @@ jobs:
runs-on: macos-latest
environment: macos-codesign
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
@@ -176,7 +172,7 @@ jobs:
echo "$MACOS_NOTARIZE_JSON" >/tmp/notarize.json
./build_tools/make_macos_pkg.sh -s -f /tmp/app.p12 \
-i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" \
-n -j /tmp/notarize.json -- -c "-DWITH_DOCS=ON"
-n -j /tmp/notarize.json
version=$(git describe)
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.app.zip" ]
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.pkg" ]

View File

@@ -13,7 +13,7 @@ jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- name: Install deps
uses: ./.github/actions/install-dependencies
@@ -32,23 +32,30 @@ jobs:
- name: make fish_run_tests
run: |
make -C build VERBOSE=1 fish_run_tests
- name: translation updates
run: |
# Generate PO files. This should not result it a change in the repo if all translations are
# up to date.
# Ensure that fish is available as an executable.
PATH="$PWD/build:$PATH" build_tools/update_translations.fish
# Show diff output. Fail if there is any.
git --no-pager diff --exit-code || { echo 'There are uncommitted changes after regenerating the gettext PO files. Make sure to update them via `build_tools/update_translations.fish` after changing source files.'; exit 1; }
ubuntu-32bit-static-pcre2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
with:
targets: "i586-unknown-linux-gnu"
- name: Update package database
run: sudo apt-get update
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_pcre: false
include_sphinx: false
- name: Install g++-multilib
run: sudo apt install g++-multilib
run: |
sudo apt install g++-multilib
- name: cmake
env:
CFLAGS: "-m32"
@@ -73,15 +80,13 @@ jobs:
RUSTFLAGS: "-Zsanitizer=address"
# RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
# All -Z options require running nightly
- uses: dtolnay/rust-toolchain@nightly
with:
# ASAN uses `cargo build -Zbuild-std` which requires the rust-src component
# this is comma-separated
components: rust-src
- name: Update package database
run: sudo apt-get update
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
@@ -123,7 +128,7 @@ jobs:
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
CARGO_NET_GIT_FETCH_WITH_CLI: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- name: Install deps
run: |
@@ -150,24 +155,22 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
- uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1, build_tools/update-dependencies.sh
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
update: true
msystem: MSYS
id: msys2
- name: Install deps
# Not using setup-msys2 `install` option to make it easier to copy/paste
run: |
pacman --noconfirm -S --needed git rust python3 diffutils tmux
- name: rebase
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
shell: cmd
pacman --noconfirm -S --needed git rust
- name: cargo build
run: |
"%MSYS2_LOCATION%\usr\bin\dash" /usr/bin/rebaseall -p -v
- name: check
env:
FISH_CHECK_LINT: false
cargo build
- name: smoketest
# We can't run `build_tools/check.sh` yet, there are just too many failures
# so this is just a quick check to make sure that fish can swim
run: |
cargo xtask check
set -x
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]
cargo test

53
.gitignore vendored
View File

@@ -20,6 +20,7 @@
*.o
*.obj
*.orig
!tests/*.out
*.out
*.pch
*.slo
@@ -35,31 +36,46 @@
Desktop.ini
Thumbs.db
ehthumbs.db
__pycache__/
.directory
.fuse_hidden*
# Artifacts from in-tree builds ("cmake .").
/build.ninja
/cargo/
/CMakeCache.txt
/CMakeFiles/
/cmake_install.cmake
# Directories that only contain transitory files from building and testing.
/doc/
/share/man/
/share/doc/
/test/
/user_doc/
# File names that can appear in the project root that represent artifacts from
# building and testing.
/FISH-BUILD-VERSION-FILE
/command_list.txt
/command_list_toc.txt
/compile_commands.json
/doc.h
/fish
/fish.pc
/fish_indent
/fish_key_reader
/fish-localization-map-cache/
/fish.pc
/fish.pc.noversion
/.ninja_log
/fish_tests
/lexicon.txt
/lexicon_filter
/toc.txt
/version
fish-build-version-witness.txt
__pycache__
# File names that can appear below the project root that represent artifacts
# from building and testing.
/doc_src/commands.hdr
/doc_src/index.hdr
/po/*.gmo
/share/__fish_build_paths.fish
/share/pkgconfig
/tests/*.tmp.*
/tests/.last-check-all-files
/.venv/
# xcode
## Build generated
@@ -67,19 +83,24 @@ __pycache__/
*.xccheckout
*.xcscmblueprin
.vscode
/build/
/DerivedData/
/build/
/tags
/xcuserdata/
xcuserdata/
# Generated by Cargo
# will have compiled files and executables
/target/
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by clangd
/.cache/
/.cache
# JetBrains editors.
.idea/

View File

@@ -1,307 +1,5 @@
fish 4.8.0 (released June 24, 2026)
===================================
Notable improvements and fixes
------------------------------
- Translatable messages defined in Rust source code can and should now be translated using `Fluent <https://projectfluent.org/>`__ instead of GNU gettext.
To make Fluent easy to work with, we have added tooling based on the new `fluent-ftl-tools <https://codeberg.org/danielrainer/fluent-ftl-tools>`__ library.
See :ref:`Contributing Translations <localization>` (:issue:`11928`).
Deprecations and removed features
---------------------------------
- Builtin :doc:`complete's <cmds/complete>` ``--command`` and ``--path`` options no longer unescape their argument.
Interactive improvements
------------------------
- History search would sometimes forget about commands after those were re-run in concurrent sessions. This has been fixed (:issue:`10300`).
- ``fish_hg_prompt``, ``fish_git_prompt`` and ``fish_fossil_prompt`` now strip control characters from VCS state read off disk, matching ``prompt_pwd``.
- Abbreviations with ``--position=anywhere`` can now be completed in argument position, not just in command position (:issue:`12630`).
- Path component movement (:kbd:`ctrl-w`) skips escaped characters.
- Completion of short option groups will now handle ``--condition`` correctly (:issue:`12821`).
- Fixed an issue where :kbd:`ctrl-c` might fail to cancel certain functions (:issue:`12802`).
- On the first run after upgrading from an older version, fish will try harder to check if the current theme matches a historical default.
If it does match, fish won't create ``~/.config/fish/conf.d/fish_frozen_theme.fish`` when upgrading from fish < 4.3.
In particular, on systems where fish version 3.x was installed originally, fish will now avoid creating that file on upgrade (:issue:`12725`).
Scripting improvements
----------------------
- ``cd`` supports the ``-L`` and ``-P`` options, like other shells, to allow specifying whether symbolic links (symlinks) are resolved when changing directories (:issue:`7206`).
- ``cd`` with a relative path will now retry using the real current directory, if ``$PWD`` has been moved (:issue:`12700`).
- Nested brace expansions now strip unquoted leading and trailing spaces from entries consistently (:issue:`12794`).
- :doc:`bind <cmds/bind>` shows the files where bindings were defined (:issue:`12504`).
Other improvements
------------------
- fish no longer creates the ``__fish_initialized`` universal variable on startup.
If you don't expect to need to downgrade to earlier versions, you can remove it with ``set --erase __fish_initialized``.
This means that fish now only creates universal variables if instructed by the user.
For distributors and developers
-------------------------------
- With the exception of the ``$CMAKE_INSTALL_PREFIX/share/fish/man`` directory, fish no longer installs files to ``$CMAKE_INSTALL_PREFIX/share/fish``.
In particular, this means that both
``$CMAKE_INSTALL_PREFIX/share/fish/completions`` and
``$CMAKE_INSTALL_PREFIX/share/fish/functions``
should no longer exist.
These directories have been ignored since fish 4.2.
If another package installs fish scripts there, they should be corrected to install to
``extra_completionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_completions.d``),
``extra_functionsdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_functions.d``) or
``extra_confdir`` (typically ``$CMAKE_INSTALL_PREFIX/share/fish/vendor_functions.d``) instead.
See also the output of ``for var in completions functions conf; pkgconf fish --variable="$var"dir; end``.
Regression fixes:
-----------------
- (from 4.4.0) Vi mode ``c,W`` key binding wrongly deleted trailing spaces (:issue:`12790`).
- (from 4.4.0) Vi mode ``x`` in :doc:`builtin read <cmds/read>` (:issue:`12724`).
- (from 4.3.3) Repeated tab would sometimes insert smartcase completions redundantly.
- (from 4.3.0) Pressing escape during command execution could insert garbage text into the command line (:issue:`12379`).
fish 4.7.1 (released May 08, 2026)
==================================
This release fixes a regression in 4.7.0 that caused the web config (``fish_config``) to fail to start (:issue:`12717`).
fish 4.7.0 (released May 05, 2026)
==================================
Deprecations and removed features
---------------------------------
- The default theme (i.e. the ``fish_color_*`` variables) is no longer set in non-interactive shells.
Interactive improvements
------------------------
- :doc:`prompt_pwd <cmds/prompt_pwd>` now strips control characters.
- Repaint events (as triggered by changes to color variables or by event handlers running ``commandline -f repaint``) no longer reset the completion pager and other transient UI states (:issue:`12683`).
- :envvar:`fish_color_valid_path` now respects background and underline colors (:issue:`12622`).
- :doc:`funced <cmds/funced>` will no longer lose work if there are parse errors multiple times without new changes to the file.
- Fixed a case where directory completions were sorted in a surprising order (:issue:`12695`).
- When at the command token, the :kbd:`alt-o` binding will now open read-only files too (:issue:`12671`).
- Private mode in-memory history (``set fish_history``) is no longer shared with :doc:`builtin read <cmds/read>` (:issue:`12662`).
Other improvements
------------------
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
- :doc:`fish_update_completions <cmds/fish_update_completions>` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
- Removing history entries via the :doc:`web-based config <cmds/fish_config>` is more intuitive.
- If :envvar:`XDG_DATA_DIRS` is empty, the default value is assumed, which means that fish will now also use configuration from paths like ``$PREFIX/share/fish/vendor_completions.d`` (:issue:`11349`).
- Some internal file descriptors were moved to number 10 or higher, to reduce risk of clashes with those used by the user in scripts.
- The wording of error messages has been made consistent, especially for builtin subcommands (:issue:`12556`).
For distributors and developers
-------------------------------
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden via ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
- ``build_tools/update_translations.fish`` has been replaced by ``cargo xtask gettext {check,new,update}`` (:issue:`12676`).
- ``cargo xtask shellcheck`` to lint shell-scripts.
Regression fixes:
-----------------
- (from 4.6) Vi mode ``dl`` (:issue:`12461`).
- (from 4.6) Backspace after newline (:issue:`12583`).
- (from 4.3.3) Long options were spuriously completed after typing short options (85e76ba3561).
- (from 3.2) ``nosuchcommand || echo hello`` executes the right hand side again (:issue:`12654`).
fish 4.6.0 (released March 28, 2026)
====================================
Notable improvements and fixes
------------------------------
- New Spanish translations (:issue:`12489`).
- New Japanese translations (:issue:`12499`).
Deprecations and removed features
---------------------------------
- The default width for emoji is switched from 1 to 2, improving the experience for users connecting to old systems from modern desktops. Users of old desktops who notice that lines containing emoji are misaligned can set ``$fish_emoji_width`` back to 1 (:issue:`12562`).
Interactive improvements
------------------------
- The tab completion pager now left-justifies the description of each column (:issue:`12546`).
- fish now supports the ``SHELL_PROMPT_PREFIX``, ``SHELL_PROMPT_SUFFIX``, and ``SHELL_WELCOME`` environment variables. The prefix and suffix are automatically prepended and appended to the left prompt, and the welcome message is displayed on startup after the greeting.
These variables are set by systemd's ``run0`` for example (:issue:`10924`).
Improved terminal support
-------------------------
- ``set_color`` is able to turn off italics, reverse mode, strikethrough and underline individually (e.g. ``--italics=off``).
- ``set_color`` learned the foreground (``--foreground`` or ``-f``) and reset (``--reset``) options.
- An error caused by slow terminal responses at macOS startup has been addressed (:issue:`12571`).
Other improvements
------------------
- Signals like ``SIGWINCH`` (as sent on terminal resize) no longer interrupt builtin output (:issue:`12496`).
- For compatibility with Bash, fish now accepts ``|&`` as alternate spelling of ``&|``, for piping both standard output and standard error (:issue:`11516`).
- ``fish_indent`` now preserves comments and newlines immediately preceding a brace block (``{ }``) (:issue:`12505`).
- A crash when suspending certain pipelines with :kbd:`ctrl-z` has been fixed (:issue:`12301`).
For distributors and developers
-------------------------------
- ``cargo xtask`` subcommands no longer panic on test failures.
Regression fixes:
-----------------
- (from 4.5.0) Intermediate ```` artifact when redrawing prompt (:issue:`12476`).
- (from 4.4.0) ``history`` honors explicitly specified ``--color=`` again (:issue:`12512`).
- (from 4.4.0) Vi mode ``dl`` and ``dh`` (:issue:`12461`).
- (from 4.3.0) Error completing of commands starting with ``-`` (:issue:`12522`).
fish 4.5.0 (released February 17, 2026)
=======================================
This is mostly a patch release for Vi mode regressions in 4.4.0 but other minor behavior changes are included as well.
Interactive improvements
------------------------
- :kbd:`ctrl-l` no longer cancels history search (:issue:`12436`).
- History search cursor positioning now works correctly with characters of arbitrary width.
Deprecations and removed features
---------------------------------
- fish no longer reads the terminfo database to alter behaviour based on the :envvar:`TERM` environment variable, and does not depend on ncurses or terminfo. The ``ignore-terminfo`` feature flag, introduced and enabled by default in fish 4.1, is now permanently enabled. fish may no longer work correctly on Data General Dasher D220 and Wyse WY-350 terminals, but should continue to work on all known terminal emulators released in the 21st century.
Regression fixes:
-----------------
- (from 4.4.0) Vi mode ``d,f`` key binding did not work (:issue:`12417`).
- (from 4.4.0) Vi mode ``c,w`` key binding wrongly deleted trailing spaces (:issue:`12443`).
- (from 4.4.0) Vi mode crash on ``c,i,w`` after accepting autosuggestion (:issue:`12430`).
- (from 4.4.0) ``fish_vi_key_bindings`` called with a mode argument produced an error (:issue:`12413`).
- (from 4.0.0) Build on Illumos (:issue:`12410`).
fish 4.4.0 (released February 03, 2026)
=======================================
Deprecations and removed features
---------------------------------
- The default fossil prompt has been disabled (:issue:`12342`).
Interactive improvements
------------------------
- The ``bind`` builtin lists mappings from all modes if ``--mode`` is not provided (:issue:`12214`).
- Line-wise autosuggestions that don't start a command are no longer shown (739b82c34db, 58e7a50de8a).
- Builtin ``history`` now assumes that :envvar:`PAGER` supports ANSI color sequences.
- fish now clears the terminal's ``FLUSHO`` flag when acquiring control of the terminal, to fix an issue caused by pressing :kbd:`ctrl-o` on macOS (:issue:`12304`).
New or improved bindings
------------------------
- Vi mode word movements (``w``, ``W``, ``e``, and ``E``) are now largely in line with Vim. The only exception is that underscores are treated as word separators (:issue:`12269`).
- New special input functions to support these movements: ``forward-word-vi``, ``kill-word-vi``, ``forward-bigword-vi``, ``kill-bigword-vi``, ``forward-word-end``, ``backward-word-end``, ``forward-bigword-end``, ``backward-bigword-end``, ``kill-a-word``, ``kill-inner-word``, ``kill-a-bigword``, and ``kill-inner-bigword``.
- Vi mode key bindings now support counts for movement and deletion commands (e.g. `d3w` or `3l`), via a new operator mode (:issue:`2192`).
- New ``catppuccin-*`` color themes.
Improved terminal support
-------------------------
- ``set_color`` learned the strikethrough (``--strikethrough`` or ``-s``) modifier.
For distributors and developers
-------------------------------
- The CMake option ``WITH_GETTEXT`` has been renamed to ``WITH_MESSAGE_LOCALIZATION``, to reflect that it toggles localization independently of the backend used in the implementation.
- New ``cargo xtask`` commands can replace some CMake workflows.
Regression fixes:
-----------------
- (from 4.1.0) Crash when autosuggesting Unicode characters with nontrivial lowercase mapping (:issue:`12326`, 78f4541116e).
- (from 4.3.0) Glitch on ``read --prompt-str ""`` (:issue:`12296`).
fish 4.3.3 (released January 07, 2026)
======================================
This release fixes the following problems identified in fish 4.3.0:
- Selecting a completion could insert only part of the token (:issue:`12249`).
- Glitch with soft-wrapped autosuggestions and :doc:`fish_right_prompt <cmds/fish_right_prompt>` (:issue:`12255`).
- Spurious echo in tmux when typing a command really fast (:issue:`12261`).
- ``tomorrow`` theme always using the light variant (:issue:`12266`).
- ``fish_config theme choose`` sometimes not shadowing themes set by e.g. webconfig (:issue:`12278`).
- The sample prompts and themes are correctly installed (:issue:`12241`).
- Last line of command output could be hidden when missing newline (:issue:`12246`).
Other improvements include:
- The ``abbr``, ``bind``, ``complete``, ``functions``, ``history`` and ``type`` commands now support a ``--color`` option to control syntax highlighting in their output. Valid values are ``auto`` (default), ``always``, or ``never``.
- Existing file paths in redirection targets such as ``> file.txt`` are now highlighted using :envvar:`fish_color_valid_path`, indicating that ``file.txt`` will be clobbered (:issue:`12260`).
fish 4.3.2 (released December 30, 2025)
=======================================
This release fixes the following problems identified in 4.3.0:
- Pre-built macOS packages failed to start due to a ``Malformed Mach-O file`` error (:issue:`12224`).
- ``extra_functionsdir`` (usually ``vendor_functions.d``) and friends were not used (:issue:`12226`).
- Sample config file ``~/.config/fish/config.fish/`` and config directories ``~/.config/fish/conf.d/``, ``~/.config/fish/completions/`` and ``~/.config/fish/functions/`` were recreated on every startup instead of only the first time fish runs on a system (:issue:`12230`).
- Spurious echo of ``^[[I`` in some scenarios (:issue:`12232`).
- Infinite prompt redraw loop on some prompts (:issue:`12233`).
- The removal of pre-built HTML docs from tarballs revealed that cross compilation is broken because we use ``${CMAKE_BINARY_DIR}/fish_indent`` for building HTML docs.
As a workaround, the new CMake build option ``FISH_INDENT_FOR_BUILDING_DOCS`` can be set to the path of a runnable ``fish_indent`` binary.
fish 4.3.1 (released December 28, 2025)
=======================================
This release fixes the following problem identified in 4.3.0:
- Possible crash after expanding an abbreviation (:issue:`12223`).
fish 4.3.0 (released December 28, 2025)
=======================================
Deprecations and removed features
---------------------------------
- fish no longer sets user-facing :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
Specifically, the ``fish_color_*``, ``fish_pager_color_*`` and ``fish_key_bindings`` variables are now set in the global scope by default.
After upgrading to 4.3.0, fish will (once and never again) migrate these universals to globals set at startup in the
``~/.config/fish/conf.d/fish_frozen_theme.fish`` and
``~/.config/fish/conf.d/fish_frozen_key_bindings.fish`` files.
We suggest that you delete those files and :ref:`set your theme <syntax-highlighting>` in ``~/.config/fish/config.fish``.
- You can still configure fish to propagate theme changes instantly; see :ref:`here <syntax-highlighting-instant-update>` for an example.
- You can still opt into storing color variables in the universal scope
via ``fish_config theme save`` though unlike ``fish_config theme choose``,
it does not support dynamic theme switching based on the terminal's color theme (see below).
- In addition to setting the variables which are explicitly defined in the given theme,
``fish_config theme choose`` now clears only color variables that were set by earlier invocations of a ``fish_config theme choose`` command
(which is how fish's default theme is set).
Scripting improvements
----------------------
- New :ref:`status language <status-language>` command allows showing and modifying language settings for fish messages without having to modify environment variables.
- When using a noninteractive fish instance to compute completions, ``commandline --cursor`` works as expected instead of throwing an error (:issue:`11993`).
- :envvar:`fish_trace` can now be set to ``all`` to also trace execution of key bindings, event handlers as well as prompt and title functions.
Interactive improvements
------------------------
- When typing immediately after starting fish, the first prompt is now rendered correctly.
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
- Prefix-matching completions are now shown even if they don't match the case typed by the user (:issue:`7944`).
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
- When using the exe name (``foo.exe``), fish will use the description and completions for ``foo`` if there are none for ``foo.exe``.
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
New or improved bindings
------------------------
- :kbd:`ctrl-w` (``backward-kill-path-component``) also deletes escaped spaces (:issue:`2016`).
- New special input functions ``backward-path-component``, ``forward-path-component`` and ``kill-path-component`` (:issue:`12127`).
Improved terminal support
-------------------------
- Themes can now be made color-theme-aware by including both ``[light]`` and ``[dark]`` sections in the :ref:`theme file <fish-config-theme-files>`.
Some default themes have been made color-theme-aware, meaning they dynamically adjust as your terminal's background color switches between light and dark colors (:issue:`11580`).
- The working directory is now reported on every fresh prompt (via OSC 7), fixing scenarios where a child process (like ``ssh``) left behind a stale working directory (:issue:`12191`).
- OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`).
- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS <status-terminal-os>`.
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around some incompatible terminals.
For distributors and developers
-------------------------------
- Tarballs no longer contain prebuilt documentation,
so building and installing documentation requires Sphinx.
To avoid users accidentally losing docs, the ``BUILD_DOCS`` and ``INSTALL_DOCS`` configuration options have been replaced with a new ``WITH_DOCS`` option.
- ``fish_key_reader`` and ``fish_indent`` are now installed as hardlinks to ``fish``, to save some space.
Regression fixes:
-----------------
- (from 4.1.0) Crash on incorrectly-set color variables (:issue:`12078`).
- (from 4.1.0) Crash when autosuggesting Unicode characters with nontrivial lowercase mapping.
- (from 4.2.0) Incorrect emoji width computation on macOS.
- (from 4.2.0) Mouse clicks and :kbd:`ctrl-l` edge cases in multiline command lines (:issue:`12121`).
- (from 4.2.0) Completions for Git remote names on some non-glibc systems.
- (from 4.2.0) Expansion of ``~$USER``.
fish ?.?.? (released ???)
=========================
fish 4.2.1 (released November 13, 2025)
=======================================
@@ -407,13 +105,15 @@ This release fixes the following regressions identified in 4.1.0:
This will not affect fish's child processes unless ``LC_MESSAGES`` was already exported.
- Some :doc:`fish_config <cmds/fish_config>` subcommands for showing prompts and themes had been broken in standalone Linux builds (those using the ``embed-data`` cargo feature), which has been fixed (:issue:`11832`).
- On Windows Terminal, we observed an issue where fish would fail to read the terminal's response to our new startup queries, causing noticeable lags and a misleading error message. A workaround has been added (:issue:`11841`).
- On Windows Terminal, we observed an issue where fish would fail to read the terminal's response to our new startup queries, causing noticeable lags and a misleading error message. A workaround has been added (:issue:`11841`).
- A WezTerm `issue breaking shifted key input <https://github.com/wezterm/wezterm/issues/6087>`__ has resurfaced on some versions of WezTerm; our workaround has been extended to cover all versions for now (:issue:`11204`).
- Fixed a crash in :doc:`the web-based configuration tool <cmds/fish_config>` when using the new underline styles (:issue:`11840`).
fish 4.1.0 (released September 27, 2025)
========================================
.. ignore for 4.1: 10929 10940 10948 10955 10965 10975 10989 10990 10998 11028 11052 11055 11069 11071 11079 11092 11098 11104 11106 11110 11140 11146 11148 11150 11214 11218 11259 11288 11299 11328 11350 11373 11395 11417 11419
Notable improvements and fixes
------------------------------
- Compound commands (``begin; echo 1; echo 2; end``) can now be written using braces (``{ echo1; echo 2 }``), like in other shells.
@@ -1479,7 +1179,7 @@ Deprecations and removed features
Like ``stderr-nocaret``, they will eventually be made read-only.
- Most ``string`` subcommands no longer append a newline to their input if the input didn't have one (:issue:`8473`, :issue:`3847`)
- fish's escape sequence removal (like for ``string length --visible`` or to figure out how wide the prompt is) no longer has special support for non-standard color sequences like from Data General terminals, e.g. the Data General Dasher D220 from 1984. This removes a bunch of work in the common case, allowing ``string length --visible`` to be much faster with unknown escape sequences. We don't expect anyone to have ever used fish with such a terminal (:issue:`8769`).
- Fish's escape sequence removal (like for ``string length --visible`` or to figure out how wide the prompt is) no longer has special support for non-standard color sequences like from Data General terminals, e.g. the Data General Dasher D220 from 1984. This removes a bunch of work in the common case, allowing ``string length --visible`` to be much faster with unknown escape sequences. We don't expect anyone to have ever used fish with such a terminal (:issue:`8769`).
- Code to upgrade universal variables from fish before 3.0 has been removed. Users who upgrade directly from fish versions 2.7.1 or before will have to set their universal variables & abbreviations again. (:issue:`8781`)
- The meaning of an empty color variable has changed (:issue:`8793`). Previously, when a variable was set but empty, it would be interpreted as the "normal" color. Now, empty color variables cause the same effect as unset variables - the general highlighting variable for that type is used instead. For example::
@@ -1490,14 +1190,14 @@ Deprecations and removed features
This makes it easier to make self-contained color schemes that don't accidentally use color that was set before.
``fish_config`` has been adjusted to set known color variables that a theme doesn't explicitly set to empty.
- ``eval`` is now a reserved keyword, so it can't be used as a function name. This follows ``set`` and ``read``, and is necessary because it can't be cleanly shadowed by a function - at the very least ``eval set -l argv foo`` breaks. fish will ignore autoload files for it, so left over ``eval.fish`` from previous fish versions won't be loaded.
- ``eval`` is now a reserved keyword, so it can't be used as a function name. This follows ``set`` and ``read``, and is necessary because it can't be cleanly shadowed by a function - at the very least ``eval set -l argv foo`` breaks. Fish will ignore autoload files for it, so left over ``eval.fish`` from previous fish versions won't be loaded.
- The git prompt in informative mode now defaults to skipping counting untracked files, as this was extremely slow. To turn it on, set :envvar:`__fish_git_prompt_showuntrackedfiles` or set the git config value "bash.showuntrackedfiles" to ``true`` explicitly (which can be done for individual repositories). The "informative+vcs" sample prompt already skipped display of untracked files, but didn't do so in a way that skipped the computation, so it should be quite a bit faster in many cases (:issue:`8980`).
- The ``__terlar_git_prompt`` function, used by the "Terlar" sample prompt, has been rebuilt as a configuration of the normal ``fish_git_prompt`` to ease maintenance, improve performance and add features (like reading per-repo git configuration). Some slight changes remain; users who absolutely must have the same behavior are encouraged to copy the old function (:issue:`9011`, :issue:`7918`, :issue:`8979`).
Scripting improvements
----------------------
- Quoted command substitution that directly follow a variable expansion (like ``echo "$var$(echo x)"``) no longer affect the variable expansion (:issue:`8849`).
- fish now correctly expands command substitutions that are preceded by an escaped dollar (like ``echo \$(echo)``). This regressed in version 3.4.0.
- Fish now correctly expands command substitutions that are preceded by an escaped dollar (like ``echo \$(echo)``). This regressed in version 3.4.0.
- ``math`` can now handle underscores (``_``) as visual separators in numbers (:issue:`8611`, :issue:`8496`)::
math 5 + 2_123_252
@@ -1517,7 +1217,7 @@ Scripting improvements
Interactive improvements
------------------------
- fish now reports a special error if a command wasn't found and there is a non-executable file by that name in :envvar:`PATH` (:issue:`8804`).
- Fish now reports a special error if a command wasn't found and there is a non-executable file by that name in :envvar:`PATH` (:issue:`8804`).
- ``less`` and other interactive commands would occasionally be stopped when run in a pipeline with fish functions; this has been fixed (:issue:`8699`).
- Case-changing autosuggestions generated mid-token now correctly append only the suffix, instead of duplicating the token (:issue:`8820`).
- ``ulimit`` learned a number of new options for the resource limits available on Linux, FreeBSD ande NetBSD, and returns a specific warning if the limit specified is not available on the active operating system (:issue:`8823`, :issue:`8786`).
@@ -1527,9 +1227,9 @@ Interactive improvements
- Since fish 3.2.0, pressing :kbd:`ctrl-d` while a command is running would end up inserting a space into the next commandline, which has been fixed (:issue:`8871`).
- A bug that caused multi-line prompts to be moved down a line when pasting or switching modes has been fixed (:issue:`3481`).
- The Web-based configuration system no longer strips too many quotes in the abbreviation display (:issue:`8917`, :issue:`8918`).
- fish started with ``--no-config`` will now use the default keybindings (:issue:`8493`)
- Fish started with ``--no-config`` will now use the default keybindings (:issue:`8493`)
- When fish inherits a :envvar:`USER` environment variable value that doesn't correspond to the current effective user ID, it will now correct it in all cases (:issue:`8879`, :issue:`8583`).
- fish sets a new :envvar:`EUID` variable containing the current effective user id (:issue:`8866`).
- Fish sets a new :envvar:`EUID` variable containing the current effective user id (:issue:`8866`).
- ``history search`` no longer interprets the search term as an option (:issue:`8853`)
- The status message when a job terminates should no longer be erased by a multiline prompt (:issue:`8817`)
@@ -1790,7 +1490,7 @@ Improved terminal support
Other improvements
------------------
- fish's test suite now uses ``ctest``, and has become much faster to run. It is now also possible to run only specific tests with targets named ``test_$filename`` - ``make test_set.fish`` only runs the set.fish test. (:issue:`7851`)
- Fish's test suite now uses ``ctest``, and has become much faster to run. It is now also possible to run only specific tests with targets named ``test_$filename`` - ``make test_set.fish`` only runs the set.fish test. (:issue:`7851`)
- The HTML version of the documentation now includes copy buttons for code examples (:issue:`8218`).
- The HTML version of the documentation and the web-based configuration tool now pick more modern system fonts instead of falling back to Arial and something like Courier New most of the time (:issue:`8632`).
- The Debian & Ubuntu package linked from fishshell.com is now a single package, rather than split into ``fish`` and ``fish-common`` (:issue:`7845`).
@@ -3389,7 +3089,7 @@ Other fixes and improvements
variables (:issue:`4200`, :issue:`4341`), executing functions, globs (:issue:`4579`),
``string`` reading from standard input (:issue:`4610`), and slicing history
(in particular, ``$history[1]`` for the last executed command).
- fishs internal wcwidth function has been updated to deal with newer
- Fishs internal wcwidth function has been updated to deal with newer
Unicode, and the width of some characters can be configured via the
``fish_ambiguous_width`` (:issue:`5149`) and ``fish_emoji_width`` (:issue:`2652`)
variables. Alternatively, a new build-time option INTERNAL_WCWIDTH
@@ -3426,7 +3126,7 @@ For distributors and developers
standard sh instead.
- The ``hostname`` command is no longer required for fish to operate.
-
fish 2.7.1 (released December 23, 2017)
=======================================
@@ -3438,7 +3138,7 @@ session (:issue:`4521`).
If you are upgrading from version 2.6.0 or before, please also review
the release notes for 2.7.0 and 2.7b1 (included below).
-
fish 2.7.0 (released November 23, 2017)
=======================================
@@ -3450,7 +3150,7 @@ from version 2.6.0 or before, please also review the release notes for
Xcode builds and macOS packages could not be produced with 2.7b1, but
this is fixed in 2.7.0.
-
fish 2.7b1 (released October 31, 2017)
======================================
@@ -4145,13 +3845,13 @@ Backward-incompatible changes
Other notable fixes and improvements
------------------------------------
- fish no longer silences errors in config.fish (:issue:`2702`)
- Fish no longer silences errors in config.fish (:issue:`2702`)
- Directory autosuggestions will now descend as far as possible if
there is only one child directory (:issue:`2531`)
- Add support for bright colors (:issue:`1464`)
- Allow Ctrl-J (``\cj``) to be bound separately from Ctrl-M
(``\cm``) (:issue:`217`)
- psub now has a “-s”/“-suffix” option to name the temporary file with
- psub now has a “-s”/“suffix” option to name the temporary file with
that suffix
- Enable 24-bit colors on select terminals (:issue:`2495`)
- Support for SVN status in the prompt (:issue:`2582`)
@@ -4175,13 +3875,13 @@ Other notable fixes and improvements
systemd-analyze, localectl, timedatectl
- and more
- fish no longer has a function called sgrep, freeing it for user
- Fish no longer has a function called sgrep, freeing it for user
customization (:issue:`2245`)
- A rewrite of the completions for cd, fixing a few bugs (:issue:`2299`, :issue:`2300`,
:issue:`562`)
- Linux VTs now run in a simplified mode to avoid issues (:issue:`2311`)
- The vi-bindings now inherit from the emacs bindings
- fish will also execute ``fish_user_key_bindings`` when in vi-mode
- Fish will also execute ``fish_user_key_bindings`` when in vi-mode
- ``funced`` will now also check $VISUAL (:issue:`2268`)
- A new ``suspend`` function (:issue:`2269`)
- Subcommand completion now works better with split /usr (:issue:`2141`)
@@ -4250,7 +3950,7 @@ Other notable fixes and improvements
- New documentation design (:issue:`1662`), which requires a Doxygen version
1.8.7 or newer to build.
- fish now defines a default directory for other packages to provide
- Fish now defines a default directory for other packages to provide
completions. By default this is
``/usr/share/fish/vendor-completions.d``; on systems with
``pkgconfig`` installed this path is discoverable with
@@ -4541,7 +4241,7 @@ Other Notable Fixes
- xsel is no longer built as part of fish. It will still be invoked if
installed separately :issue:`633`
- \__fish_filter_mime no longer spews :issue:`628`
- The -no-execute option to fish no longer falls over when reaching the
- The no-execute option to fish no longer falls over when reaching the
end of a block :issue:`624`
- fish_config knows how to find fish even if its not in the $PATH :issue:`621`
- A leading space now prevents writing to history, as is done in bash

View File

@@ -24,12 +24,12 @@ include(cmake/Rust.cmake)
# Work around issue where archive-built libs go in the wrong place.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
HINTS
$ENV{SPHINX_DIR}
PATH_SUFFIXES bin
DOC "Sphinx documentation generator")
# Set up the machinery around FISH-BUILD-VERSION-FILE
# This defines the FBVF variable.
include(Version)
# Set up the docs.
include(cmake/Docs.cmake)
# Tell Cargo where our build directory is so it can find Cargo.toml.
set(VARS_FOR_CARGO
@@ -42,7 +42,9 @@ set(VARS_FOR_CARGO
"CARGO_TARGET_DIR=${FISH_RUST_BUILD_DIR}"
"CARGO_BUILD_RUSTC=${Rust_COMPILER}"
"${FISH_PCRE2_BUILDFLAG}"
"RUSTFLAGS=$ENV{RUSTFLAGS} ${rust_debugflags}"
"FISH_SPHINX=${SPHINX_EXECUTABLE}"
"FISH_USE_PREBUILT_DOCS=${USE_PREBUILT_DOCS}"
)
# Let fish pick up when we're running out of the build directory without installing
@@ -53,46 +55,40 @@ add_definitions(-DCMAKE_SOURCE_DIR="${REAL_CMAKE_SOURCE_DIR}")
set(build_types Release RelWithDebInfo Debug "")
if(NOT "${CMAKE_BUILD_TYPE}" IN_LIST build_types)
message(WARNING "Unsupported build type ${CMAKE_BUILD_TYPE}. If this doesn't build, try one of Release, RelWithDebInfo or Debug")
message(WARNING "Unsupported build type ${CMAKE_BUILD_TYPE}. If this doesn't build, try one of Release, RelWithDebInfo or Debug")
endif()
add_custom_target(
fish ALL
# Define a function to build and link dependencies.
function(CREATE_TARGET target)
add_custom_target(
${target} ALL
COMMAND
"${CMAKE_COMMAND}" -E
env ${VARS_FOR_CARGO}
${Rust_CARGO}
build --bin fish
$<$<CONFIG:Release>:--release>
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
--target ${Rust_CARGO_TARGET}
--no-default-features
--features=${FISH_CARGO_FEATURES}
${CARGO_FLAGS}
&&
"${CMAKE_COMMAND}" -E
copy "${rust_target_dir}/${rust_profile}/fish" "${CMAKE_CURRENT_BINARY_DIR}"
"${CMAKE_COMMAND}" -E
env ${VARS_FOR_CARGO}
${Rust_CARGO}
build --bin ${target}
$<$<CONFIG:Release>:--release>
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
--target ${Rust_CARGO_TARGET}
--no-default-features
--features=${FISH_CARGO_FEATURES}
${CARGO_FLAGS}
&&
"${CMAKE_COMMAND}" -E
copy "${rust_target_dir}/${rust_profile}/${target}" "${CMAKE_CURRENT_BINARY_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
USES_TERMINAL
)
)
endfunction(CREATE_TARGET)
function(CREATE_LINK target)
add_custom_target(
${target} ALL
DEPENDS fish
COMMAND ln -f fish ${target}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
)
endfunction(CREATE_LINK)
# Define fish.
create_target(fish)
# Define fish_indent.
create_link(fish_indent)
create_target(fish_indent)
# Define fish_key_reader.
create_link(fish_key_reader)
# Set up the docs.
include(cmake/Docs.cmake)
create_target(fish_key_reader)
# Set up tests.
include(cmake/Tests.cmake)

View File

@@ -1,10 +1,10 @@
####################
Contributing To fish
Contributing To Fish
####################
This document tells you how you can contribute to fish.
fish is free and open source software, distributed under the terms of the GPLv2.
Fish is free and open source software, distributed under the terms of the GPLv2.
Contributions are welcome, and there are many ways to contribute!
@@ -21,10 +21,10 @@ Archives are available at https://lists.sr.ht/~krobelus/fish-shell/.
GitHub
======
fish is available on GitHub, at https://github.com/fish-shell/fish-shell.
Fish is available on Github, at https://github.com/fish-shell/fish-shell.
First, you'll need an account there, and you'll need a git clone of fish.
Fork it on GitHub and then run::
Fork it on Github and then run::
git clone https://github.com/<USERNAME>/fish-shell.git
@@ -50,24 +50,7 @@ Guidelines
In short:
- Be conservative in what you need (keep to the agreed minimum supported Rust version, limit new dependencies)
- Use automated tools to help you (``cargo xtask check``)
Commit History
==============
We use a linear, `recipe-style <https://www.bitsnbites.eu/git-history-work-log-vs-recipe/>`__ history.
Every commit should pass our checks.
We do not want "fixup" commits in our history.
If you notice an issue with a commit in a pull request, or get feedback suggesting changes,
you should rewrite the commit history and fix the relevant commits directly,
instead of adding new "fixup" commits.
When a pull request is ready, we rebase it on top of the current master branch,
so don't be shy about rewriting the history of commits which are not on master yet.
Rebasing (not merging) your pull request on the latest version of master is also welcome, especially if it resolves conflicts.
If you're using Git, consider using `jj <https://www.jj-vcs.dev/>`__ to make this easier.
If a commit should close an issue, add a ``Fixes #<issue-number>`` line at the end of the commit description.
- Use automated tools to help you (``build_tools/check.sh``)
Contributing completions
========================
@@ -105,24 +88,14 @@ Contributing documentation
==========================
The documentation is stored in ``doc_src/``, and written in ReStructured Text and built with Sphinx.
The builtins and various functions shipped with fish are documented in ``doc_src/cmds/``.
To build an HTML version of the docs locally, run::
To build it locally, run from the main fish-shell directory::
cargo xtask html-docs
sphinx-build -j 8 -b html -n doc_src/ /tmp/fish-doc/
will output to ``target/fish-docs/html`` or, if you use CMake::
cmake --build build -t sphinx-docs
will output to ``build/cargo/fish-docs/html/``. You can also run ``sphinx-build`` directly, which allows choosing the output directory::
sphinx-build -j auto -b html doc_src/ /tmp/fish-doc/
will output HTML docs to ``/tmp/fish-doc``.
After building them, you can open the HTML docs in a browser and see that it looks okay.
which will build the docs as html in /tmp/fish-doc. You can open it in a browser and see that it looks okay.
The builtins and various functions shipped with fish are documented in doc_src/cmds/.
Code Style
==========
@@ -133,14 +106,14 @@ For formatting, we use:
- ``fish_indent`` (shipped with fish) for fish script
- ``ruff format`` for Python
To reformat files, there is an xtask
To reformat files, there is a script
::
cargo xtask format --all
cargo xtask format somefile.rs some.fish
build_tools/style.fish --all
build_tools/style.fish somefile.rs some.fish
fish Script Style Guide
Fish Script Style Guide
-----------------------
1. All fish scripts, such as those in the *share/functions* and *tests*
@@ -154,7 +127,7 @@ fish Script Style Guide
for public vars or ``_fish`` for private vars to minimize the
possibility of name clashes with user defined vars.
Configuring Your Editor for fish Scripts
Configuring Your Editor for Fish Scripts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you use Vim: Install `vim-fish <https://github.com/dag/vim-fish>`__,
@@ -202,7 +175,7 @@ The source code for fish includes a large collection of tests. If you
are making any changes to fish, running these tests is a good way to make
sure the behaviour remains consistent and regressions are not
introduced. Even if you dont run the tests on your machine, they will
still be run via GitHub Actions.
still be run via Github Actions.
You are strongly encouraged to add tests when changing the functionality
of fish, especially if you are fixing a bug to help ensure there are no
@@ -248,72 +221,42 @@ In this example we're in the root of the workspace and have run ``cargo build``
To run all tests and linters, use::
cargo xtask check
.. _localization:
build_tools/check.sh
Contributing Translations
=========================
fish can localize messages present in its Rust source code,
as well as messages in the various fish scripts present in this repository.
The latter include a large amount of automatically identified messages,
originating for example from fish function descriptions.
When translating, prioritize the messages from the Rust source code.
fish uses two different localization systems:
`GNU gettext <https://www.gnu.org/software/gettext/>`__ and `Fluent <https://projectfluent.org/>`__.
The former is used for all messages from fish scripts.
For messages from the Rust source code, we are in the process of replacing gettext with Fluent.
At the moment, both are used side-by-side,
with some messages localized with gettext and the others with Fluent.
We use custom tools for extracting messages from source files and for gettext localization at runtime.
Fish uses GNU gettext to translate messages from English to other languages.
We use custom tools for extracting messages from source files and to localize at runtime.
This means that we do not have a runtime dependency on the gettext library.
It also means that some of gettext's features are not supported, such as message context and plurals.
We expect all files to be UTF-8-encoded.
It also means that some features are not supported, such as message context and plurals.
We also expect all files to be UTF-8-encoded.
In practice, this should not matter much for contributing translations.
Translation sources for gettext are stored in the ``localization/po`` directory and named ``ll_CC.po``,
whereas Fluent uses the ``localization/fluent`` directory and names of the shape ``ll_CC.ftl``,
where ``ll`` is the two (or possibly three) letter ISO 639-1 language code of the target language
(e.g. ``pt`` for Portuguese).
``CC`` is an ISO 3166 country/territory code, (e.g. ``BR`` for Brazil).
An example for a valid name is ``pt_BR.po`` for gettext and ``pt_BR.ftl`` for Fluent,
indicating Brazilian Portuguese.
Translation sources are
stored in the ``po`` directory, named ``ll_CC.po``, where ``ll`` is the
two (or possibly three) letter ISO 639-1 language code of the target language
(e.g. ``pt`` for Portuguese). ``CC`` is an ISO 3166 country/territory code,
(e.g. ``BR`` for Brazil).
An example for a valid name is ``pt_BR.po``, indicating Brazilian Portuguese.
These are the files you will interact with when adding translations.
In some cases, we also use language identifiers without a county code, i.e. ``ll.po``/``ll.ftl``.
Which variant is chosen involves various trade-offs for fallback behavior.
If you want to add a new language or language variant, feel free to ask about this.
Generally, if people who understand any variant of the language
are expected to understand the version you add
and there are no existing translations for another variant of the language,
it probably makes sense to omit the country code,
otherwise to use it for all variants of the language.
Adding translations for a new language
--------------------------------------
Creating new translations for gettext requires the gettext tools.
More specifically, you will need ``msguniq``, ``msgmerge``, and ``msgmerge``
for creating translations for a new language.
To create a PO file for a new language ``ll_CC``, run::
Creating new translations requires the Gettext tools.
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
language.
To create a new translation, run::
cargo xtask gettext new ll_CC
build_tools/update_translations.fish po/ll_CC.po
This will create a new PO file in ``localization/po/``
containing all messages available for translation.
This will create a new PO file containing all messages available for translation.
If the file already exists, it will be updated.
For Fluent, it is sufficient to create a new, empty file
with the language-appropriate name in ``localization/fluent``.
After modifying a translation file, you can recompile fish,
and it will integrate the modifications you made.
After modifying a PO file, you can recompile fish, and it will integrate the modifications you made.
This requires that the ``msgfmt`` utility is installed (comes as part of ``gettext``).
For messages localized with Fluent, recompiling fish is not necessary when you use a debug build.
Then, restarting fish is sufficient for seeing updated translations.
It is important that the ``localize-messages`` Cargo feature is enabled, which it is by default.
It is important that the ``localize-messages`` cargo feature is enabled, which it is by default.
You can explicitly enable it using::
cargo build --features=localize-messages
@@ -326,26 +269,7 @@ or within the running fish shell::
set LANG pt_BR.utf8
Alternatively, you can also use the built-in ``status language`` command, e.g.::
status language set pt_BR
Use
::
status language list-available
to see a list of the available language identifiers.
This might also be helpful for checking that your new translations are recognized as expected.
Note that using environment variables enables fallback behavior,
e.g. if you specify ``LANG=de_DE.utf8`` and we do not have a ``de_DE`` catalog but a ``de`` catalog,
you will see messages from the latter.
With ``status language``, only exact matches are supported,
giving you more control over the fallback order.
If ``status language`` is used, it overrides the environment variable configuration.
For more options regarding how to choose languages via environment variables, see
For more options regarding how to choose languages, see
`the corresponding gettext documentation
<https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html>`__.
One neat thing you can do is set a list of languages to check for translations in the order defined
@@ -353,19 +277,14 @@ using the ``LANGUAGE`` variable, e.g.::
set LANGUAGE pt_BR de_DE
or using::
status language set pt_BR de
to try to translate messages to Portuguese, if that fails try German, and if that fails too you will
see the default English version.
see the English version defined in the source code.
Modifying existing translations
-------------------------------
If you want to work on translations for a language which already has translations, it
is sufficient to edit the existing files.
No other changes are necessary.
If you want to work on translations for a language which already has a corresponding ``po`` file, it
is sufficient to edit this file. No other changes are necessary.
After recompiling fish, you should be able to see your translations in action. See the previous
section for details.
@@ -376,7 +295,7 @@ Editing PO files
Many tools are available for editing translation files, including
command-line and graphical user interface programs. For simple use, you can use your text editor.
Open up the PO file, for example ``localization/po/sv.po``, and you'll see something like::
Open up the PO file, for example ``po/sv.po``, and you'll see something like::
msgid "%s: No suitable job\n"
msgstr ""
@@ -389,7 +308,7 @@ For example::
msgid "%s: No suitable job\n"
msgstr "%s: Inget passande jobb\n"
Any ``%s`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string must have the same placeholders in the same order.
Any ``%s`` or ``%d`` are placeholders that fish will use for formatting at runtime. It is important that they match - the translated string should have the same placeholders in the same order.
Also any escaped characters, like that ``\n`` newline at the end, should be kept so the translation has the same behavior.
@@ -398,166 +317,30 @@ Our tests run ``msgfmt --check-format /path/to/file``, so they would catch misma
Be cautious about blindly updating an existing translation file.
``msgid`` strings should never be updated manually, only by running the appropriate script.
Editing FTL files
-----------------
To get familiar with Fluent's FTL format,
you can read `Fluent's guide <https://projectfluent.org/fluent/guide/>`__.
The core principle is that each message has an ID.
This ID is specified in the source code.
At runtime, Fluent checks FTL files according to the user's language settings
to try to map the ID to a localized message.
All messages are localized into English because the source code only contains IDs, not proper messages.
Check ``localization/fluent/en.ftl`` to see which IDs are in use and what the corresponding messages are.
Some messages receive arguments, called variables in Fluent.
In Fluent, each variable has a name, which allows reordering them,
so they can appear in the order which makes most sense for the language.
The general format of a variable in an FTL file is ``{ $variable_name }``.
The Fluent ecosystem is not as mature as gettext, meaning there is less available tooling.
Therefore, we provide some of our own tools.
Running ``cargo xtask fluent`` provides an overview.
For translators, the following can be useful::
cargo xtask fluent format
to make FTL files conform to our expected format,
::
cargo xtask fluent show-missing
to show which message IDs do not have a translation yet, and
::
cargo xtask fluent check
to run checks on the FTL files, which can catch some mistakes.
Each of these commands takes optional path arguments,
so if you are working on a certain file like ``pt_BR.ftl``,
you might want to use
::
cargo xtask fluent check localization/fluent/pt_BR.ftl
from the repository root directory,
or, if you are in the ``localization/fluent`` directory,
::
cargo xtask fluent check pt_BR.ftl
If you want formatting in your editor,
::
cargo --quiet xtask fluent format -
might be useful, which reads FTL text from stdin and writes a formatted version to stdout,
or a copy of stdin if formatting failed.
Instead of invoking Cargo each time, you could also invoke the ``xtask`` binary if it exists.
A simple format-on-write setup in Vim:
.. code:: vim
function FormatFTL()
let cursor = getpos('.')
:%!cargo --quiet xtask fluent format -
call setpos('.', cursor)
endfunction
augroup ftl
autocmd!
autocmd! BufWritePre *.ftl :call FormatFTL()
augroup END
or equivalently in Lua for NeoVim:
.. code:: lua
local augroup_ftl = vim.api.nvim_create_augroup("ftl", { clear = true })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup_ftl,
pattern = "*.ftl",
callback = function()
local cursor = vim.fn.getpos(".")
vim.cmd("%!cargo --quiet xtask fluent format -")
vim.fn.setpos(".", cursor)
end,
})
There is also a `Vim plugin <https://github.com/projectfluent/fluent.vim>`__ for syntax highlighting.
Modifications to strings in source files (gettext-only)
-------------------------------------------------------
Modifications to strings in source files
----------------------------------------
If a string changes in the sources, the old translations will no longer work.
They will be preserved in the PO files, but commented-out (starting with ``#~``).
If you add/remove/change a translatable strings in a source file,
run ``cargo xtask gettext update`` to propagate this to all translation files (``localization/po/*.po``).
run ``build_tools/update_translations.fish`` to propagate this to all translation files (``po/*.po``).
This is only relevant for developers modifying the source files of fish or fish scripts.
Note translations for messages which are no longer present in the sources will be deleted from the PO files.
If the source string changed in a way which should not affect translations,
consider updating the ``msgid`` in the PO files such that translations are preserved.
Setting Code Up For Translations
--------------------------------
All non-debug messages output for user consumption should be marked for translation.
In Rust, this requires the use of the ``localize!`` macro for Fluent localization, e.g.:
.. code:: rust
localize!(
"some-message-id" = "English version of the message. Must be a valid Fluent message definition. Example variables: { $var1 }, { $var2 }",
var1 = "some string",
var2 = 42,
);
where the first key-value pair is the message's Fluent ID and the English version of the message.
The remaining key-value pairs specify Fluent variables and their values.
These must match the variables used in the message definition.
For changing message IDs or associated variable names accross all FTL files, the
All non-debug messages output for user consumption should be marked for
translation. In Rust, this requires the use of the ``wgettext!`` or ``wgettext_fmt!``
macros:
::
cargo xtask fluent rename
streams.out.append(wgettext_fmt!("%s: There are no jobs\n", argv[0]));
command can be helpful.
The definitions in the Rust sources are the source of truth for the English version of the messages.
Our tooling automatically generates a corresponding ``en.po`` file from these definitions,
but that file is fully auto-generated and manual modifications to it are not supported.
The `en.po` file exists to allow using Fluent tooling which expects such a file,
and to be able to detect changes to the definitions in the Rust sources.
Note that changes to the message definitions have consequences for translations.
Our tooling automatically detects such changes.
In some cases, they can be resolved automatically,
e.g. when a message ID no longer exists, the translations will be deleted.
If no automatic resolution is possible, annotations will be added to the affected translations,
indicating that they need developer attention.
As long as such annotations are present, our checks will not pass.
To resolve them, use ``cargo xtask fluent resolve-outdated``,
or, for languages into which you can translate,
update the translation and remove the annotation manually.
Legacy gettext localization uses the ``wgettext!`` or ``wgettext_fmt!`` macros.
New code should use Fluent instead.
.. code:: rust
streams.out.appendln(&wgettext_fmt!("%s: There are no jobs", argv[0]));
For explicit localization in fish scripts,
all messages must be enclosed in single or double quote characters
for our message extraction script to find them.
They must also be translated via a command substitution.
This means that the following are **not** valid:
All messages in fish script must be enclosed in single or double quote
characters for our message extraction script to find them.
They must also be translated via a command substitution. This means
that the following are **not** valid:
::

1198
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,99 +2,63 @@
members = ["crates/*"]
[workspace.package]
edition = "2024"
# To build revisions that use Corrosion (those before 2024-01), use CMake 3.19, Rustc 1.78 and Rustup 1.27.
rust-version = "1.85"
edition = "2024"
repository = "https://github.com/fish-shell/fish-shell"
# see doc_src/license.rst for details
# don't forget to update COPYING and debian/copyright too
license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
[workspace.dependencies]
anstyle = "1.0.13"
anyhow = "1.0.102"
assert_matches = "1.5.0"
bitflags = "2.5.0"
cc = "1.0.94"
cfg-if = "1.0.3"
clap = { version = "4.5.54", features = ["derive"] }
clap_complete = { version = "4.6.4", features = ["unstable-dynamic"] }
errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" }
fish-color = { path = "crates/color" }
fish-common = { path = "crates/common" }
fish-fallback = { path = "crates/fallback" }
fish-feature-flags = { path = "crates/feature-flags" }
fish-fluent = { path = "crates/fluent" }
fish-fluent-extraction = { path = "crates/fluent-extraction" }
fish-gettext = { path = "crates/gettext" }
fish-gettext-extraction = { path = "crates/gettext-extraction" }
fish-gettext-maps = { path = "crates/gettext-maps" }
fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" }
fish-localization = { path = "crates/localization" }
fish-localization-extraction = { path = "crates/localization-extraction" }
fish-printf = { path = "crates/printf", features = ["widestring"] }
fish-tempfile = { path = "crates/tempfile" }
fish-util = { path = "crates/util" }
fish-wcstringutil = { path = "crates/wcstringutil" }
fish-wgetopt = { path = "crates/wgetopt" }
fish-widecharwidth = { path = "crates/widecharwidth" }
fish-widestring = { path = "crates/widestring" }
fluent = { git = "https://github.com/danielrainer/fluent-rs", rev = "cf712bced280b217b6307edabc2089b3e57204ab" }
fluent-ftl-tools = { git = "https://codeberg.org/danielrainer/fluent-ftl-tools", rev = "5917664c8f2e4928ef1e480ff5c13bbe1e226066" }
fluent-syntax = { git = "https://github.com/danielrainer/fluent-rs", rev = "cf712bced280b217b6307edabc2089b3e57204ab" }
ignore = "0.4.25"
itertools = "0.14.0"
libc = "0.2.177"
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
# files as of 22 April 2024.
lru = "0.18.0"
nix = { version = "0.31.1", default-features = false, features = [
"event",
"fs",
"hostname",
"inotify",
"process",
"resource",
"signal",
"term",
"user",
lru = "0.13.0"
nix = { version = "0.30.1", default-features = false, features = [
"event",
"inotify",
"resource",
"fs",
] }
num-traits = "0.2.19"
once_cell = "1.19.0"
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
"utf32",
"utf32",
] }
phf = { version = "0.13", default-features = false }
phf_shared = "0.13"
phf_codegen = "0.13"
phf = { version = "0.12", default-features = false }
phf_codegen = { version = "0.12" }
portable-atomic = { version = "1", default-features = false, features = [
"fallback",
"fallback",
] }
proc-macro2 = "1.0"
rand = { version = "0.10.1", default-features = false, features = [
"thread_rng",
# Don't use the "getrandom" feature as it requires "getentropy" which was not
# available on macOS < 10.12. We can enable "getrandom" when we raise the
# minimum supported version to 10.12.
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
rsconf = "0.2.2"
rust-embed = { version = "8.7.2", features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
] }
regex = "1.12.3"
rsconf = "0.3.0"
rust-embed = { version = "8.11.0", features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
] }
rustc_version = "0.4.1"
serial_test = { version = "3", default-features = false }
strum_macros = "0.28.0"
syn = { version = "2.0.117", default-features = false, features = ["parsing"] }
unic-langid = "0.9.6"
# We need 0.9.0 specifically for some crash fixes.
terminfo = "0.9.0"
widestring = "1.2.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"
unix_path = "1.0.1"
walkdir = "2.5.0"
widestring = "1.2.0"
xterm-color = "1.0.1"
[profile.release]
overflow-checks = true
@@ -106,39 +70,26 @@ debug = true
[package]
name = "fish"
version = "4.8.0"
version = "4.2.1-snapshot"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
license.workspace = true
# see doc_src/license.rst for details
# don't forget to update COPYING and debian/copyright too
license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
homepage = "https://fishshell.com"
readme = "README.rst"
[dependencies]
assert_matches.workspace = true
bitflags.workspace = true
cfg-if.workspace = true
errno.workspace = true
fish-build-helper.workspace = true
fish-build-man-pages = { workspace = true, optional = true }
fish-color.workspace = true
fish-common.workspace = true
fish-fallback.workspace = true
fish-feature-flags.workspace = true
fish-fluent.workspace = true
fish-fluent-extraction = { workspace = true, optional = true }
fish-gettext = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true }
fish-localization = { workspace = true, optional = true }
fish-gettext-maps = { workspace = true, optional = true }
fish-printf.workspace = true
fish-tempfile.workspace = true
fish-util.workspace = true
fish-wcstringutil.workspace = true
fish-wgetopt.workspace = true
fish-widecharwidth.workspace = true
fish-widestring.workspace = true
fluent.workspace = true
itertools.workspace = true
libc.workspace = true
lru.workspace = true
macro_rules_attribute = "0.2.2"
@@ -146,25 +97,26 @@ nix.workspace = true
num-traits.workspace = true
once_cell.workspace = true
pcre2.workspace = true
phf = { workspace = true, optional = true }
rand.workspace = true
strum_macros.workspace = true
xterm-color.workspace = true
terminfo.workspace = true
widestring.workspace = true
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic.workspace = true
[target.'cfg(windows)'.dependencies]
rust-embed = { workspace = true, features = [
"deterministic-timestamps",
"debug-embed",
"include-exclude",
"interpolate-folder-path",
rust-embed = { workspace = true, optional = true, features = [
"deterministic-timestamps",
"debug-embed",
"include-exclude",
"interpolate-folder-path",
] }
[target.'cfg(not(windows))'.dependencies]
rust-embed = { workspace = true, features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
rust-embed = { workspace = true, optional = true, features = [
"deterministic-timestamps",
"include-exclude",
"interpolate-folder-path",
] }
[dev-dependencies]
@@ -176,7 +128,6 @@ fish-build-helper.workspace = true
fish-gettext-mo-file-parser.workspace = true
phf_codegen = { workspace = true, optional = true }
rsconf.workspace = true
rustc_version.workspace = true
[target.'cfg(windows)'.build-dependencies]
unix_path.workspace = true
@@ -198,65 +149,41 @@ name = "fish_key_reader"
path = "src/bin/fish_key_reader.rs"
[features]
default = ["embed-manpages", "localize-messages"]
default = ["embed-data", "localize-messages"]
benchmark = []
embed-manpages = ["dep:fish-build-man-pages"]
# This feature is used to enable extracting Fluent IDs from the source code for localization check.
# For normal builds, it should be disabled.
# It only needs to be enabled if checking Fluent Translation List (FTL) files is desired.
fluent-extract = ["fish-fluent/fluent-extract", "dep:fish-fluent-extraction"]
# This feature is used to enable extracting messages from the source code for localization.
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
# desired. This happens for the `gettext` xtask, which is also invoked via `cargo xtask check`.
# There should not be a need to enable this feature manually.
gettext-extract = ["dep:fish-gettext-extraction"]
embed-data = ["dep:rust-embed", "dep:fish-build-man-pages"]
# Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at
# build time.
localize-messages = [
"fish-fluent/localize-messages",
"dep:fish-gettext",
"dep:fish-localization",
]
localize-messages = ["dep:phf", "dep:fish-gettext-maps"]
# This feature is used to enable extracting messages from the source code for localization.
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
# desired. This happens when running tests via `build_tools/check.sh` and when calling
# `build_tools/update_translations.fish`, so there should not be a need to enable it manually.
gettext-extract = ["dep:fish-gettext-extraction"]
# The following features are auto-detected by the build-script and should not be enabled manually.
asan = []
tsan = []
[workspace.lints]
rust.unknown_lints = { level = "allow", priority = -1 }
rust.non_camel_case_types = "allow"
rust.non_upper_case_globals = "allow"
rust.unknown_lints = "allow"
rust.unstable_name_collisions = "allow"
rustdoc.private_intra_doc_links = "allow"
[workspace.lints.clippy]
assigning_clones = "warn"
cloned_instead_of_copied = "warn"
explicit_into_iter_loop = "warn"
format_push_string = "warn"
implicit_clone = "warn"
len_without_is_empty = "allow" # we're not a library crate
let_and_return = "allow"
manual_assert = "warn"
manual_range_contains = "allow"
map_unwrap_or = "warn"
mut_mut = "warn"
needless_lifetimes = "allow"
new_without_default = "allow"
option_map_unit_fn = "allow"
ptr_offset_by_literal = "warn"
redundant_clone = "warn"
ref_option = "warn"
semicolon_if_nothing_returned = "warn"
stable_sort_primitive = "warn"
str_to_string = "warn"
unnecessary_semicolon = "warn"
unused_trait_names = "warn"
clippy.len_without_is_empty = "allow" # we're not a library crate
clippy.let_and_return = "allow"
clippy.manual_range_contains = "allow"
clippy.needless_lifetimes = "allow"
clippy.needless_return = "allow"
clippy.new_without_default = "allow"
clippy.option_map_unit_fn = "allow"
# We do not want to use the e?print(ln)?! macros.
# These lints flag their use.
# In the future, they might change to flag other methods of printing.
print_stdout = "deny"
print_stderr = "deny"
# usage in tests is fine since it avoids interacting with TopicMonitor
# and is configured in clippy.toml with `allow-print-in-tests`
clippy.print_stdout = "deny"
clippy.print_stderr = "deny"
[lints]
workspace = true

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM centos:latest
# Build dependency
RUN yum update -y &&\
yum install -y epel-release &&\
yum install -y clang cmake3 gcc-c++ make &&\
yum clean all
# Test dependency
RUN yum install -y expect vim-common
ADD . /src
WORKDIR /src
# Build fish
RUN cmake3 . &&\
make &&\
make install

View File

@@ -7,7 +7,7 @@
CMAKE ?= cmake
GENERATOR ?= $(shell (which ninja > /dev/null 2> /dev/null && echo Ninja) || \
echo 'Unix Makefiles')
echo 'Unix Makefiles')
prefix ?= /usr/local
PREFIX ?= $(prefix)
@@ -34,7 +34,7 @@ all: .begin build/fish
.PHONY: .begin
.begin:
@which $(CMAKE) > /dev/null 2> /dev/null || \
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
.PHONY: build/fish
build/fish: build/$(BUILDFILE)

View File

@@ -1,5 +1,9 @@
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status|
=================================================================================
.. |Cirrus CI| image:: https://api.cirrus-ci.com/github/fish-shell/fish-shell.svg?branch=master
:target: https://cirrus-ci.com/github/fish-shell/fish-shell
:alt: Cirrus CI Build Status
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status| |Cirrus CI|
=============================================================================================
fish is a smart and user-friendly command line shell for macOS, Linux,
and the rest of the family. fish includes features like syntax
@@ -27,13 +31,13 @@ macOS
fish can be installed:
- using `Homebrew <https://brew.sh/>`__: ``brew install fish``
- using `Homebrew <http://brew.sh/>`__: ``brew install fish``
- using `MacPorts <https://www.macports.org/>`__:
``sudo port install fish``
- using the `installer from fishshell.com <https://fishshell.com/>`__
- as a `standalone app from fishshell.com <https://fishshell.com/>`__
Note: The minimum supported macOS version is 10.12.
Note: The minimum supported macOS version is 10.10 "Yosemite".
Packages for Linux
~~~~~~~~~~~~~~~~~~
@@ -62,7 +66,7 @@ Windows
for Linux with the instructions for the appropriate distribution
listed above under “Packages for Linux”, or from source with the
instructions below.
- fish can also be installed on all versions of Windows using
- Fish can also be installed on all versions of Windows using
`Cygwin <https://cygwin.com/>`__ or `MSYS2 <https://github.com/Berrysoft/fish-msys2>`__.
Building from source
@@ -113,7 +117,7 @@ Dependencies
Compiling fish requires:
- Rust (version 1.85 or later), including cargo
- Rust (version 1.85 or later)
- CMake (version 3.15 or later)
- a C compiler (for system feature detection and the test helper binary)
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
@@ -153,14 +157,11 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
- Rust_COMPILER=path - the path to rustc. If not set, cmake will check $PATH and ~/.cargo/bin
- Rust_CARGO=path - the path to cargo. If not set, cmake will check $PATH and ~/.cargo/bin
- Rust_CARGO_TARGET=target - the target to pass to cargo. Set this for cross-compilation.
- WITH_DOCS=ON|OFF - whether to build the documentation. By default, this is ON when Sphinx is installed.
- FISH_INDENT_FOR_BUILDING_DOCS - useful for cross-compilation.
Set this to the path to the ``fish_indent`` executable to use for building HTML docs.
By default, ``${CMAKE_BINARY_DIR}/fish_indent`` will be used.
If that's not runnable on the compile host,
you can build a native one with ``cargo build --bin fish_indent`` and set this to ``$PWD/target/debug/fish_indent``.
- BUILD_DOCS=ON|OFF - whether to build the documentation. This is automatically set to OFF when Sphinx isn't installed.
- INSTALL_DOCS=ON|OFF - whether to install the docs. This is automatically set to on when BUILD_DOCS is or prebuilt documentation is available (like when building in-tree from a tarball).
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
- WITH_MESSAGE_LOCALIZATION=ON|OFF - whether to include translations.
- MAC_CODESIGN_ID=String|OFF - the codesign ID to use on Mac, or "OFF" to disable codesigning.
- WITH_GETTEXT=ON|OFF - whether to include translations.
- extra_functionsdir, extra_completionsdir and extra_confdir - to compile in an additional directory to be searched for functions, completions and configuration snippets
Building fish with Cargo
@@ -184,14 +185,13 @@ You can also install Sphinx another way and drop the ``uv run --no-managed-pytho
This will place standalone binaries in ``~/.cargo/bin/``, but you can move them wherever you want.
To disable translations, disable the ``localize-messages`` feature by passing ``--no-default-features --features=embed-manpages`` to cargo.
To disable translations, disable the ``localize-messages`` feature by passing ``--no-default-features --features=embed-data`` to cargo.
You can also link this build statically (but not against glibc) and move it to other computers.
Here are the remaining advantages of a full installation, as currently done by CMake:
- Man pages like ``fish(1)`` installed in standard locations, easily accessible from outside fish.
- Separate files for builtins (e.g. ``$PREFIX/share/fish/man/man1/abbr.1``).
- A local copy of the HTML documentation, typically accessed via the ``help`` fish function.
In Cargo builds, ``help`` will redirect to `<https://fishshell.com/docs/current/>`__
- Ability to use our CMake options extra_functionsdir, extra_completionsdir and extra_confdir,
@@ -217,5 +217,5 @@ There is also a fish tag on Stackoverflow, but it is typically a poor fit.
Found a bug? Have an awesome idea? Please `open an
issue <https://github.com/fish-shell/fish-shell/issues/new>`__.
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/actions/workflows/test.yml/badge.svg
:target: https://github.com/fish-shell/fish-shell/actions/workflows/test.yml
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/workflows/make%20test/badge.svg
:target: https://github.com/fish-shell/fish-shell/actions

230
build.rs
View File

@@ -1,15 +1,13 @@
use fish_build_helper::{
env_var, fish_build_dir, target_os, target_os_is_apple, target_os_is_bsd, target_os_is_cygwin,
workspace_root,
};
use fish_build_helper::{env_var, fish_build_dir, workspace_root};
use rsconf::Target;
use std::env;
use std::path::{Path, PathBuf};
fn main() {
let is_nightly =
rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly;
rsconf::declare_cfg("nightly", is_nightly);
fn canonicalize<P: AsRef<Path>>(path: P) -> PathBuf {
std::fs::canonicalize(path).unwrap()
}
fn main() {
setup_paths();
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
@@ -19,14 +17,14 @@ fn main() {
"FISH_RESOLVED_BUILD_DIR",
// 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.
fish_build_dir().canonicalize().unwrap().to_str().unwrap(),
canonicalize(fish_build_dir()).to_str().unwrap(),
);
// 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.
rsconf::set_env_value(
"CARGO_MANIFEST_DIR",
workspace_root().canonicalize().unwrap().to_str().unwrap(),
canonicalize(workspace_root()).to_str().unwrap(),
);
// Some build info
@@ -34,11 +32,22 @@ fn main() {
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap());
let version = &get_version(&env::current_dir().unwrap());
// 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
rsconf::set_env_value("FISH_BUILD_VERSION", &get_version());
rsconf::set_env_value("FISH_BUILD_VERSION", version);
fish_build_helper::rebuild_if_embedded_path_changed("share");
// safety: single-threaded code.
unsafe { std::env::set_var("FISH_BUILD_VERSION", version) };
// These are necessary if built with embedded functions,
// but only in release builds (because rust-embed in debug builds reads from the filesystem).
#[cfg(feature = "embed-data")]
#[cfg(any(windows, not(debug_assertions)))]
rsconf::rebuild_if_path_changed("share");
#[cfg(feature = "gettext-extract")]
rsconf::rebuild_if_env_changed("FISH_GETTEXT_EXTRACTION_FILE");
let build = cc::Build::new();
let mut target = Target::new_from(build).unwrap();
@@ -64,48 +73,77 @@ fn main() {
/// `Cargo.toml`) behind a feature we just enabled.
///
/// [0]: https://github.com/rust-lang/cargo/issues/5499
#[rustfmt::skip]
fn detect_cfgs(target: &mut Target) {
for (name, handler) in [
// Ignore the first entry, it just sets up the type inference.
// Ignore the first entry, it just sets up the type inference. Model new entries after the
// second line.
("", &(|_: &Target| false) as &dyn Fn(&Target) -> bool),
("apple", &(|_| target_os_is_apple())),
("bsd", &(|_| target_os_is_bsd())),
("cygwin", &(|_| target_os_is_cygwin())),
("have_eventfd", &|target| {
// FIXME: NetBSD 10 has eventfd, but the libc crate does not expose it.
if target_os() == "netbsd" {
false
} else {
target.has_header("sys/eventfd.h")
}
}),
("have_localeconv_l", &|target| {
("apple", &detect_apple),
("bsd", &detect_bsd),
("using_cmake", &|_| option_env!("FISH_CMAKE_BINARY_DIR").is_some()),
("use_prebuilt_docs", &|_| env_var("FISH_USE_PREBUILT_DOCS").is_some_and(|v| v == "TRUE") ),
("cygwin", &detect_cygwin),
("small_main_stack", &has_small_stack),
// See if libc supports the thread-safe localeconv_l(3) alternative to localeconv(3).
("localeconv_l", &|target| {
target.has_symbol("localeconv_l")
}),
("have_pipe2", &|target| target.has_symbol("pipe2")),
("have_posix_spawn", &|target| {
if matches!(target_os().as_str(), "openbsd" | "android") {
// OpenBSD's posix_spawn returns status 127 instead of erroring with ENOEXEC when faced with a
// shebang-less script. Disable posix_spawn on OpenBSD.
//
// Android is broken for unclear reasons
false
} else {
target.has_header("spawn.h")
("FISH_USE_POSIX_SPAWN", &|target| {
target.has_header("spawn.h")
}),
("HAVE_PIPE2", &|target| {
target.has_symbol("pipe2")
}),
("HAVE_EVENTFD", &|target| {
// FIXME: NetBSD 10 has eventfd, but the libc crate does not expose it.
if cfg!(target_os = "netbsd") {
false
} else {
target.has_header("sys/eventfd.h")
}
}),
("small_main_stack", &has_small_stack),
("using_cmake", &|_| {
option_env!("FISH_CMAKE_BINARY_DIR").is_some()
}),
("waitstatus_signal_ret", &|target| {
("HAVE_WAITSTATUS_SIGNAL_RET", &|target| {
target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"])
}),
] {
rsconf::declare_cfg(name, handler(target));
rsconf::declare_cfg(name, handler(target))
}
}
fn detect_apple(_: &Target) -> bool {
cfg!(any(target_os = "ios", target_os = "macos"))
}
fn detect_cygwin(_: &Target) -> bool {
// Cygwin target is usually cross-compiled.
env_var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin"
}
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
/// `#[cfg(bsd)]`.
///
/// 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.
fn detect_bsd(_: &Target) -> bool {
// Instead of using `uname`, we can inspect the TARGET env variable set by Cargo. This lets us
// support cross-compilation scenarios.
let mut target = env_var("TARGET").unwrap();
if !target.chars().all(|c| c.is_ascii_lowercase()) {
target = target.to_ascii_lowercase();
}
let is_bsd = target.ends_with("bsd") || target.ends_with("dragonfly");
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))]
assert!(is_bsd, "Target incorrectly detected as not BSD!");
is_bsd
}
/// 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.
@@ -163,25 +201,41 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
}
let prefix = overridable_path("PREFIX", |env_prefix| {
Some(PathBuf::from(env_prefix.unwrap_or("/usr/local".to_owned())))
Some(PathBuf::from(
env_prefix.unwrap_or("/usr/local".to_string()),
))
})
.unwrap();
overridable_path("SYSCONFDIR", |env_sysconfdir| {
Some(join_if_relative(
&prefix,
env_sysconfdir.unwrap_or("/etc/".to_owned()),
env_sysconfdir.unwrap_or(
// Embedded builds use "/etc," not "$PREFIX/etc".
if cfg!(feature = "embed-data") {
"/etc/"
} else {
"etc/"
}
.to_string(),
),
))
});
let default_ok = !cfg!(feature = "embed-data");
let datadir = overridable_path("DATADIR", |env_datadir| {
env_datadir.map(|p| join_if_relative(&prefix, p))
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| {
env_bindir.map(|p| join_if_relative(&prefix, p))
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| {
env_docdir.map(|p| {
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"),
@@ -191,15 +245,79 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
});
}
fn get_version() -> String {
fn get_version(src_dir: &Path) -> String {
use std::fs::read_to_string;
use std::process::Command;
String::from_utf8(
Command::new("build_tools/git_version_gen.sh")
.output()
.unwrap()
.stdout,
)
.unwrap()
.trim_ascii_end()
.to_owned()
if let Some(var) = env_var("FISH_BUILD_VERSION") {
return var;
}
let path = src_dir.join("version");
if let Ok(strver) = read_to_string(path) {
return strver;
}
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() {
// If it contains a ".", we have a proper version like "3.7",
// or "23.2.1-1234-gfab1234"
if rev.contains('.') {
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;
}
}
// 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>> {
let workspace_root = workspace_root();
let gitdir = workspace_root.join(".git");
let jjdir = workspace_root.join(".jj");
let commit_id = if gitdir.exists() {
// .git/HEAD contains ref: refs/heads/branch
let headpath = gitdir.join("HEAD");
let headstr = read_to_string(headpath)?;
let headref = headstr.split(' ').nth(1).unwrap().trim();
// .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];
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.")
}

View File

@@ -8,29 +8,11 @@ if [ "$FISH_CHECK_LINT" = false ]; then
lint=false
fi
case "$(uname)" in
MSYS*)
is_cygwin=true
cygwin_var=MSYS
;;
CYGWIN*)
is_cygwin=true
cygwin_var=CYGWIN
;;
*)
is_cygwin=false
;;
esac
check_dependency_versions=false
if [ "${FISH_CHECK_DEPENDENCY_VERSIONS:-false}" != false ]; then
check_dependency_versions=true
fi
green='\e[0;32m'
yellow='\e[1;33m'
reset='\e[m'
if $check_dependency_versions; then
command -v curl
command -v jq
@@ -51,22 +33,13 @@ fi
cargo() {
subcmd=$1
shift
if [ -n "$FISH_CHECK_RUST_TOOLCHAIN" ]; then
# shellcheck disable=2086
command cargo "+$FISH_CHECK_RUST_TOOLCHAIN" "$subcmd" $cargo_args "$@"
else
# shellcheck disable=2086
command cargo "$subcmd" $cargo_args "$@"
fi
# shellcheck disable=2086
command cargo "$subcmd" $cargo_args "$@"
}
# shellcheck disable=2317,2329
cleanup () {
if [ -n "$fluent_extraction_dir" ] && [ -e "$fluent_extraction_dir" ]; then
rm -r "$fluent_extraction_dir"
fi
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
rm -r "$gettext_template_dir"
if [ -n "$template_file" ] && [ -e "$template_file" ]; then
rm "$template_file"
fi
}
@@ -91,84 +64,23 @@ if [ -n "$FISH_TEST_MAX_CONCURRENCY" ]; then
export CARGO_BUILD_JOBS="$FISH_TEST_MAX_CONCURRENCY"
fi
fluent_extraction_dir=$(mktemp -d)
gettext_template_dir=$(mktemp -d)
template_file=$(mktemp)
(
# shellcheck disable=2030
export FISH_FLUENT_EXTRACTION_DIR="$fluent_extraction_dir"
# shellcheck disable=2030
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
cargo build --workspace --all-targets --features=fluent-extract,gettext-extract
export FISH_GETTEXT_EXTRACTION_FILE="$template_file"
cargo build --workspace --all-targets --features=gettext-extract
)
if $lint; then
if command -v cargo-deny >/dev/null; then
cargo deny --all-features --locked --exclude-dev check licenses
fi
if command -v shellcheck >/dev/null || { test -n "$CI" && ! $is_cygwin; }; then
cargo xtask shellcheck
fi
PATH="$build_dir:$PATH" cargo xtask format --all --check
for features in "" --no-default-features --all-features; do
PATH="$build_dir:$PATH" "$workspace_root/build_tools/style.fish" --all --check
for features in "" --no-default-features; do
cargo clippy --workspace --all-targets $features
done
cargo xtask gettext --rust-extraction-dir="$gettext_template_dir" check
fi
# An outdated `en.ftl` or translations using variables not provided by the source could crash fish,
# so don't treat this as a lint.
cargo xtask fluent check --from-source="$fluent_extraction_dir"
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
# are dynamically linked against Rust's `std-xxx.dll` instead of being
# statically link as they usually are.
# On Cygwin, `PATH`is not properly updated to point to the `std-xxx.dll`
# location, so we have to do it manually.
# See:
# - https://github.com/rust-lang/rust/issues/149050
# - https://github.com/msys2/MSYS2-packages/issues/5784
(
if $is_cygwin; then
PATH="$PATH:$(rustc --print target-libdir)"
export PATH
fi
# shellcheck disable=2031
export FISH_FLUENT_EXTRACTION_DIR="$fluent_extraction_dir"
cargo test --no-default-features --workspace --all-targets
)
cargo test --no-default-features --workspace --all-targets
cargo test --doc --workspace
if $lint; then
cargo doc --workspace --no-deps
fi
system_tests() {
"$workspace_root/tests/test_driver.py" "$build_dir" "$@"
}
if $is_cygwin; then
# shellcheck disable=2059
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
(
export "$cygwin_var"=winsymlinks
system_tests
)
# shellcheck disable=2059
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
(
# Only redo the tests that use `ln` to saves some time
export "$cygwin_var"=
# shellcheck disable=2046
system_tests $(grep -l -E '\bln\b' -r tests/checks/)
)
else
# shellcheck disable=2059
printf "=== Running ${green}integration tests${reset}\n"
system_tests
cargo doc --workspace
fi
FISH_GETTEXT_EXTRACTION_FILE=$template_file "$workspace_root/tests/test_driver.py" "$build_dir"
exit
}

147
build_tools/fish_xgettext.fish Executable file
View File

@@ -0,0 +1,147 @@
#!/usr/bin/env fish
#
# Tool to generate gettext messages template file.
# Writes to stdout.
# Intended to be called from `update_translations.fish`.
argparse use-existing-template= -- $argv
or exit $status
begin
# Write header. This is required by msguniq.
# Note that this results in the file being overwritten.
# This is desired behavior, to get rid of the results of prior invocations
# of this script.
begin
echo 'msgid ""'
echo 'msgstr ""'
echo '"Content-Type: text/plain; charset=UTF-8\n"'
echo ""
end
set -g workspace_root (path resolve (status dirname)/..)
set -l rust_extraction_file
if set -l --query _flag_use_existing_template
set rust_extraction_file $_flag_use_existing_template
else
set rust_extraction_file (mktemp)
# We need to build to ensure that the proc macro for extracting strings runs.
FISH_GETTEXT_EXTRACTION_FILE=$rust_extraction_file cargo check --no-default-features --features=gettext-extract
or exit 1
end
function mark_section
set -l section_name $argv[1]
echo 'msgid "fish-section-'$section_name'"'
echo 'msgstr ""'
echo ''
end
mark_section tier1-from-rust
# Get rid of duplicates and sort.
msguniq --no-wrap --sort-output $rust_extraction_file
or exit 1
if not set -l --query _flag_use_existing_template
rm $rust_extraction_file
end
function extract_fish_script_messages_impl
set -l regex $argv[1]
set -e argv[1]
# Using xgettext causes more trouble than it helps.
# This is due to handling of escaping in fish differing from formats xgettext understands
# (e.g. POSIX shell strings).
# We work around this issue by manually writing the file content.
# Steps:
# 1. We extract strings to be translated from the relevant files and drop the rest. This step
# depends on the regex matching the entire line, and the first capture group matching the
# string.
# 2. We unescape. This gets rid of some escaping necessary in fish strings.
# 3. The resulting strings are sorted alphabetically. This step is optional. Not sorting would
# result in strings from the same file appearing together. Removing duplicates is also
# optional, since msguniq takes care of that later on as well.
# 4. Single backslashes are replaced by double backslashes. This results in the backslashes
# being interpreted as literal backslashes by gettext tooling.
# 5. Double quotes are escaped, such that they are not interpreted as the start or end of
# a msgid.
# 6. We transform the string into the format expected in a PO file.
cat $argv |
string replace --filter --regex $regex '$1' |
string unescape |
sort -u |
sed -E -e 's_\\\\_\\\\\\\\_g' -e 's_"_\\\\"_g' -e 's_^(.*)$_msgid "\1"\nmsgstr ""\n_'
end
function extract_fish_script_messages
set -l tier $argv[1]
set -e argv[1]
if not set -q argv[1]
return
end
# This regex handles explicit requests to translate a message. These are more important to translate
# than messages which should be implicitly translated.
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
mark_section "$tier-from-script-explicitly-added"
extract_fish_script_messages_impl $explicit_regex $argv
# This regex handles descriptions for `complete` and `function` statements. These messages are not
# particularly important to translate. Hence the "implicit" label.
set -l implicit_regex '^(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
mark_section "$tier-from-script-implicitly-added"
extract_fish_script_messages_impl $implicit_regex $argv
end
set -g share_dir $workspace_root/share
set -l tier1 $share_dir/config.fish
set -l tier2
set -l tier3
for file in $share_dir/completions/*.fish $share_dir/functions/*.fish
# set -l tier (string match -r '^# localization: .*' <$file)
set -l tier (string replace -rf -m1 \
'^# localization: (.*)$' '$1' <$file)
if set -q tier[1]
switch "$tier"
case tier1 tier2 tier3
set -a $tier $file
case 'skip*'
case '*'
echo >&2 "$file:1 unexpected localization tier: $tier"
exit 1
end
continue
end
set -l dirname (path basename (path dirname $file))
set -l command_name (path basename --no-extension $file)
if test $dirname = functions &&
string match -q -- 'fish_*' $command_name
set -a tier1 $file
continue
end
if test $dirname != completions
echo >&2 "$file:1 missing localization tier for function file"
exit 1
end
if test -e $workspace_root/doc_src/cmds/$command_name.rst
set -a tier1 $file
else
set -a tier3 $file
end
end
extract_fish_script_messages tier1 $tier1
extract_fish_script_messages tier2 $tier2
extract_fish_script_messages tier3 $tier3
end |
# At this point, all extracted strings have been written to stdout,
# starting with the ones taken from the Rust sources,
# followed by strings explicitly marked for translation in fish scripts,
# and finally the strings from fish scripts which get translated implicitly.
# Because we do not eliminate duplicates across these categories,
# we do it here, since other gettext tools expect no duplicates.
msguniq --no-wrap

View File

@@ -1,22 +1,70 @@
#!/bin/sh
# Originally from the git sources (GIT-VERSION-GEN)
# Presumably (C) Junio C Hamano <junkio@cox.net>
# Reused under GPL v2.0
# Modified for fish by David Adam <zanchey@ucc.gu.uwa.edu.au>
set -e
# Find the fish directory as two levels up from script directory.
FISH_BASE_DIR="$( cd "$( dirname "$( dirname "$0" )" )" && pwd )"
DEF_VER=unknown
git_permission_failed=0
version=$(
awk <"$FISH_BASE_DIR/Cargo.toml" -F'"' '$1 == "version = " { print $2 }'
)
if git_version=$(
GIT_CEILING_DIRECTORIES=$FISH_BASE_DIR/.. \
git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
if [ "$git_version" = "${git_version#"$version"}" ]; then
version=$version-g$git_version
# First see if there is a version file (included in release tarballs),
# then try git-describe, then default.
if test -f version
then
VN=$(cat version) || VN="$DEF_VER"
else
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
:
else
version=$git_version
if test $? = 128; then
# Current git versions return status 128
# when run in a repo owned by another user.
# Even for describe and everything.
# This occurs for `sudo make install`.
git_permission_failed=1
fi
VN="$DEF_VER"
fi
fi
echo "$version"
# If the first param is --stdout, then output to stdout and exit.
if test "$1" = '--stdout'
then
echo $VN
exit 0
fi
# Set the output directory as either the first param or cwd.
test -n "$1" && OUTPUT_DIR=$1/ || OUTPUT_DIR=
FBVF="${OUTPUT_DIR}FISH-BUILD-VERSION-FILE"
if test "$VN" = unknown && test -r "$FBVF" && test "$git_permission_failed" = 1
then
# HACK: Git failed, so we keep the current version file.
# This helps in case you built fish as a normal user
# and then try to `sudo make install` it.
date +%s > "${OUTPUT_DIR}"fish-build-version-witness.txt
exit 0
fi
if test -r "$FBVF"
then
VC=$(cat "$FBVF")
else
VC="unset"
fi
# Maybe output the FBVF
# It looks like "2.7.1-621-ga2f065e6"
test "$VN" = "$VC" || {
echo >&2 "$VN"
echo "$VN" >"$FBVF"
}
# Output the fish-build-version-witness.txt
# See https://cmake.org/cmake/help/v3.4/policy/CMP0058.html
date +%s > "${OUTPUT_DIR}"fish-build-version-witness.txt

View File

@@ -1 +1,3 @@
leak:fish_fluent::AVAILABLE_LANGUAGES
# LSAN can detect leaks tracing back to __asan::AsanThread::ThreadStart (probably caused by our
# threads not exiting before their TLS dtors are called). Just ignore it.
leak:AsanThread

View File

@@ -1,83 +0,0 @@
#!/bin/sh
# This script takes a source tarball (from build_tools/make_tarball.sh) and a vendor tarball (from
# build_tools/make_vendor_tarball.sh, generated if not present), and produces:
# * Appropriately-named symlinks to look like a Debian package
# * Debian .changes and .dsc files with plain names ($version-1) and supported Ubuntu prefixes
# ($version-1~somedistro)
# * An RPM spec file
# By default, input and output files go in ~/fish_built, but this can be controlled with the
# FISH_ARTEFACT_PATH environment variable.
{
set -e
version=$1
[ -n "$version" ] || { echo "Version number required as argument" >&2; exit 1; }
[ -n "$DEB_SIGN_KEYID$DEB_SIGN_KEYFILE" ] ||
echo "Warning: neither DEB_SIGN_KEYID or DEB_SIGN_KEYFILE environment variables are set; you
will need a signing key for the author of the most recent debian/changelog entry." >&2
workpath=${FISH_ARTEFACT_PATH:-~/fish_built}
source_tarball="$workpath"/fish-"$version".tar.xz
vendor_tarball="$workpath"/fish-"$version"-vendor.tar.xz
[ -e "$source_tarball" ] || { echo "Missing source tarball, expected at $source_tarball" >&2; exit 1; }
cd "$workpath"
# Unpack the sources
tar xf "$source_tarball"
sourcepath="$workpath"/fish-"$version"
# Generate the vendor tarball if it is not already present
[ -e "$vendor_tarball" ] || (cd "$sourcepath"; build_tools/make_vendor_tarball.sh;)
# This step requires network access, so do it early in case it fails
# sh has no real array support
ubuntu_versions=$(uv run --script "$sourcepath"/build_tools/supported_ubuntu_versions.py)
# Write the specfile
[ -e "$workpath"/fish.spec ] && { echo "Cowardly refusing to overwite an existing fish.spec" >&2;
exit 1; }
rpmversion=$(echo "$version" |sed -e 's/-/+/' -e 's/-/./g')
sed -e "s/@version@/$version/g" -e "s/@rpmversion@/$rpmversion/g" \
< "$sourcepath"/fish.spec.in > "$workpath"/fish.spec
# Make the symlinks for Debian
ln -s "$source_tarball" "$workpath"/fish_"$version".orig.tar.xz
ln -s "$vendor_tarball" "$workpath"/fish_"$version".orig-cargo-vendor.tar.xz
# Set up the Debian source tree
cd "$sourcepath"
mkdir cargo-vendor
tar -C cargo-vendor -x -f "$vendor_tarball"
cp -r contrib/debian debian
# The vendor tarball contains a new .cargo/config.toml, which has the
# vendoring overrides appended to it. dpkg-source will add this as a
# patch using the flags in debian/
cp cargo-vendor/.cargo/config.toml .cargo/config.toml
# Update the Debian changelog
# The release scripts do this for release builds - skip if it has already been done
if head -n1 debian/changelog | grep --invert-match --quiet --fixed-strings "$version"; then
debchange --newversion "$version-1" --distribution unstable "Snapshot build"
fi
# Builds the "plain" Debian package
# debuild runs lintian, which takes ten minutes to run over the vendor directories
# just use dpkg-buildpackage directly
dpkg-buildpackage --build=source -d
# Build the Ubuntu packages
# deb-reversion does not work on source packages, so do the whole thing ourselves
for series in $ubuntu_versions; do
sed -i -e "1 s/$version-1)/$version-1~$series)/" -e "1 s/unstable/$series/" debian/changelog
dpkg-buildpackage --build=source -d
sed -i -e "1 s/$version-1~$series)/$version-1)/" -e "1 s/$series/unstable/" debian/changelog
done
}

View File

@@ -25,11 +25,9 @@ NOTARIZE=
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.12'
cmake_args=()
while getopts "c:sf:i:p:e:nj:" opt; do
while getopts "sf:i:p:e:nj:" opt; do
case $opt in
c) cmake_args+=("$OPTARG");;
s) SIGN=1;;
f) P12_APP_FILE=$(realpath "$OPTARG");;
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
@@ -49,7 +47,7 @@ if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
usage
fi
VERSION=$(build_tools/git_version_gen.sh)
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
echo "Version is $VERSION"
@@ -67,7 +65,6 @@ do_cmake() {
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF \
"${cmake_args[@]}" \
"$@" \
"$SRC_DIR"
}
@@ -82,16 +79,17 @@ do_cmake() {
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
}
# Build for x86-64 but do not install; instead we will make a fat binary inside the root.
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
{ cd "$PKGDIR/build_x86_64" \
&& do_cmake -DRust_CARGO_TARGET=x86_64-apple-darwin \
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
# Fatten it up.
FILE=$PKGDIR/root/usr/local/bin/fish
X86_FILE=$PKGDIR/build_x86_64/$(basename "$FILE")
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
chmod 755 "$FILE"
# Fatten them up.
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
chmod 755 "$FILE"
done
if test -n "$SIGN"; then
echo "Signing executables"
@@ -104,7 +102,9 @@ if test -n "$SIGN"; then
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
(set +x; rcodesign sign "${ARGS[@]}" "$PKGDIR"/root/usr/local/bin/fish)
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
done
fi
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
@@ -125,13 +125,15 @@ fi
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
# Make the app's /usr/local/bin/fish binary universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
cd "$PKGDIR/build_arm64"
FILE=fish.app/Contents/Resources/base/usr/local/bin/fish
X86_FILE=$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
# macho-universal-create screws up the permissions.
chmod 755 "$FILE"
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
# macho-universal-create screws up the permissions.
chmod 755 "$FILE"
done
if test -n "$SIGN"; then
echo "Signing app"

View File

@@ -1,42 +1,88 @@
#!/bin/sh
# Script to generate a tarball
# We use git to output a tree. But we also want to build the user documentation
# and put that in the tarball, so that nobody needs to have sphinx installed
# to build it.
# Outputs to $FISH_ARTEFACT_PATH or ~/fish_built by default
# Exit on error
set -e
# We will generate a tarball with a prefix "fish-VERSION"
# git can do that automatically for us via git-archive
# but to get the documentation in, we need to make a symlink called "fish-VERSION"
# and tar from that, so that the documentation gets the right prefix
# Use Ninja if available, as it automatically parallelises
BUILD_TOOL="make"
BUILD_GENERATOR="Unix Makefiles"
if command -v ninja >/dev/null; then
BUILD_TOOL="ninja"
BUILD_GENERATOR="Ninja"
fi
# We need GNU tar as that supports the --mtime and --transform options
TAR=notfound
for try in tar gtar gnutar; do
if $try -Pcf /dev/null --mtime now /dev/null >/dev/null 2>&1; then
TAR=$try
break
fi
done
if [ "$TAR" = "notfound" ]; then
echo 'No suitable tar (supporting --mtime) found as tar/gtar/gnutar in PATH'
exit 1
fi
# Get the current directory, which we'll use for symlinks
wd="$PWD"
# Get the version
VERSION=$(build_tools/git_version_gen.sh)
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
tag_creation_date=$(
# If not dirty (i.e. we're building an immutable tag), pin the build date.
if [ "$VERSION" = "$(git describe)" ]; then
git log --format=%ad '--date=format:%b %d, %Y' -1
fi
)
prefix=fish-$VERSION
path=${FISH_ARTEFACT_PATH:-~/fish_built}/$prefix.tar.xz
# The name of the prefix, which is the directory that you get when you untar
prefix="fish-$VERSION"
tmpdir=$(mktemp -d)
manifest=$tmpdir/Cargo.toml
lockfile=$tmpdir/Cargo.lock
# The path where we will output the tar file
# Defaults to ~/fish_built
path=${FISH_ARTEFACT_PATH:-~/fish_built}/$prefix.tar
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml >"$manifest"
awk -v version="$VERSION" '
/^name = "fish"$/ { ok=1 }
ok == 1 && /^version = ".*"$/ {
ok = 2;
$0 = "version = \"" version "\"";
}
{print}
' \
Cargo.lock >"$lockfile"
# Clean up stuff we've written before
rm -f "$path" "$path".xz
git archive \
--prefix="$prefix/" \
--add-virtual-file="$prefix/Cargo.toml:$(cat "$manifest")" \
--add-virtual-file="$prefix/Cargo.lock:$(cat "$lockfile")" \
HEAD |
xz >"$path"
# git starts the archive
git archive --format=tar --prefix="$prefix"/ HEAD > "$path"
rm "$manifest"
rm "$lockfile"
rmdir "$tmpdir"
# tarball out the documentation, generate a version file
PREFIX_TMPDIR=$(mktemp -d)
cd "$PREFIX_TMPDIR"
echo "$VERSION" > version
cmake -G "$BUILD_GENERATOR" -DCMAKE_BUILD_TYPE=Debug "$wd"
mkdir $PWD/user_doc/src
FISH_SPHINX_BUILD_DATE=$tag_creation_date \
FISH_SPHINX_HELP_SECTIONS_OUTPUT=$PWD/user_doc/src/help_sections.rs \
$BUILD_TOOL doc
TAR_APPEND="$TAR --append --file=$path --mtime=now --owner=0 --group=0 \
--mode=g+w,a+rX --transform s/^/$prefix\//"
$TAR_APPEND --no-recursion user_doc
$TAR_APPEND user_doc/html user_doc/man user_doc/src/help_sections.rs
$TAR_APPEND version
cd -
rm -r "$PREFIX_TMPDIR"
# xz it
xz "$path"
# Output what we did, and the sha256 hash
echo "Tarball written to $path"
openssl dgst -sha256 "$path"
echo "Tarball written to $path".xz
openssl dgst -sha256 "$path".xz

View File

@@ -8,11 +8,25 @@
# Exit on error
set -e
# We need GNU tar as that supports the --mtime and --transform options
TAR=notfound
for try in tar gtar gnutar; do
if $try -Pcf /dev/null --mtime now /dev/null >/dev/null 2>&1; then
TAR=$try
break
fi
done
if [ "$TAR" = "notfound" ]; then
echo 'No suitable tar (supporting --mtime) found as tar/gtar/gnutar in PATH'
exit 1
fi
# Get the current directory, which we'll use for telling Cargo where to find the sources
wd="$PWD"
# Get the version from git-describe
VERSION=$(build_tools/git_version_gen.sh)
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
# The name of the prefix, which is the directory that you get when you untar
prefix="fish-$VERSION"
@@ -28,14 +42,8 @@ rm -f "$path" "$path".xz
PREFIX_TMPDIR=$(mktemp -d)
cd "$PREFIX_TMPDIR"
# Add .cargo/config.toml. This means that the caller may need to remove that file from the tarball.
# See e4674cd7b5f (.cargo/config.toml: exclude from tarball, 2025-01-12)
mkdir .cargo
{
cat "$wd"/.cargo/config.toml
cargo vendor --manifest-path "$wd/Cargo.toml"
} > .cargo/config.toml
cargo vendor --manifest-path "$wd/Cargo.toml" > .cargo/config.toml
tar cfvJ "$path".xz vendor .cargo

View File

@@ -1,28 +1,28 @@
<html>
<head>
<style>
body {
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
code, tt {
font-family: ui-monospace, Menlo, monospace;
}
</style>
</head>
<body>
<p>
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
</p>
<p>
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
</p>
<p>
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
</p>
<p>
<code>chsh -s /usr/local/bin/fish</code>
</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.com/fish-shell/fish-shell/">GitHub</a>.</p>
</body>
<head>
<style>
body {
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
code, tt {
font-family: ui-monospace, Menlo, monospace;
}
</style>
</head>
<body>
<p>
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
</p>
<p>
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
</p>
<p>
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
</p>
<p>
<code>chsh -s /usr/local/bin/fish</code>
</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.org/fish-shell/fish-shell/">GitHub</a>.</p>
</body>
</html>

View File

@@ -4,14 +4,14 @@
if test $# -eq 0
then
echo "usage: $0 shellname [shellname ...]"
exit 1
echo "usage: $0 shellname [shellname ...]"
exit 1
fi
scriptname=$(basename "$0")
if [ "$(id -u)" -ne 0 ]; then
echo "${scriptname} must be run as root"
exit 1
echo "${scriptname} must be run as root"
exit 1
fi
file=/etc/shells

View File

@@ -2,6 +2,6 @@
echo "Removing any previous installation"
pkgutil --pkg-info "${INSTALL_PKG_SESSION_ID}" && pkgutil --only-files --files "${INSTALL_PKG_SESSION_ID}" | while read -r installed
do rm -v "${DSTVOLUME}${installed}"
do rm -v "${DSTVOLUME}${installed}"
done
echo "... removed"

View File

@@ -47,8 +47,8 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
num_authors=$(wc -l <"$relnotes_tmp/committers-now")
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
printf %s \
"This release brings $num_commits new commits since $previous_version," \
" contributed by $num_authors authors, $num_new_authors of which are new faces."
"This release comprises $num_commits commits since $previous_version," \
" contributed by $num_authors authors, $num_new_authors of which are new committers."
echo
echo
)
@@ -86,13 +86,10 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
echo
echo 'Download links:'
echo 'To download the source code for fish, we suggest the file named ``fish-'"$version"'.tar.xz``.'
# shellcheck disable=2016
echo 'The file downloaded from ``Source code (tar.gz)`` will not build correctly.'
# shellcheck disable=2016
echo 'A GPG signature using `this key <'"${FISH_GPG_PUBLIC_KEY_URL:-???}"'>`__ is available as ``fish-'"$version"'.tar.xz.asc``.'
echo 'A GPG signature using the key published at '"${FISH_GPG_PUBLIC_KEY_URL:-???}"' is available as ``fish-'"$version"'.tar.xz.asc``.'
echo
echo 'The files called ``fish-'"$version"'-linux-*.tar.xz`` contain'
# shellcheck disable=2016
echo '`standalone fish binaries <https://github.com/fish-shell/fish-shell/?tab=readme-ov-file#building-fish-with-cargo>`__'
echo 'for any Linux with the given CPU architecture.'
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst

View File

@@ -18,18 +18,15 @@ fi
[ -n "$version" ]
for tool in \
cmake \
bundle \
diff \
gh \
gpg \
jq \
ninja \
ruby \
tar \
timeout \
uv \
xz \
; do
if ! command -v "$tool" >/dev/null; then
echo >&2 "$0: missing command: $1"
@@ -72,7 +69,7 @@ integration_branch=$(
--format='%(refname:strip=2)'
)
[ -n "$integration_branch" ] ||
git merge-base --is-ancestor "$remote"/master HEAD
git merge-base --is-ancestor $remote/master HEAD
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
@@ -83,39 +80,21 @@ sed -i \
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
CHANGELOG.rst
CreateCommit() {
git commit -m "$1
CommitVersion() {
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline
git add CHANGELOG.rst Cargo.toml Cargo.lock
git commit -m "$2
Created by ./build_tools/release.sh $version"
}
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline # bumps the version in Cargo.lock
if [ "$1" = "$version" ]; then
# debchange is a Debian script to manage the Debian changelog, but
# it's too annoying to install everywhere. Just do it by hand.
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
fish (${version}-1) stable; urgency=medium
CommitVersion "$version" "Release $version"
* Release of new version $version.
See https://github.com/fish-shell/fish-shell/releases/tag/$version for details.
-- $committer $(date -R)
EOF
mv contrib/debian/changelog.new contrib/debian/changelog
git add contrib/debian/changelog
fi
git add CHANGELOG.rst Cargo.toml Cargo.lock
CreateCommit "Release $version"
# Tags must be full objects, not lightweight tags, for
# git_version-gen.sh to work.
git -c "user.signingKey=$committer" \
tag --sign --message="Release $version" "$version"
tag --sign --message="Release $version" $version
git push "$remote" "$version"
git push $remote $version
TIMEOUT=
gh() {
@@ -142,7 +121,7 @@ fish_tar_xz=fish-$version.tar.xz
(
local_tarball=$tmpdir/local-tarball
mkdir "$local_tarball"
FISH_ARTEFACT_PATH=$local_tarball ./build_tools/make_tarball.sh
FISH_ARTEFACT_PATH=$local_tarball uv run ./build_tools/make_tarball.sh
cd "$local_tarball"
tar xf "$fish_tar_xz"
)
@@ -170,28 +149,17 @@ actual_tag_oid=$(git ls-remote "$remote" |
gh release upload "$version" "$fish_tar_xz.asc"
)
(
cd "$tmpdir/local-tarball/fish-$version"
uv --no-managed-python venv
# shellcheck disable=1091
. .venv/bin/activate
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .
ninja doc
)
CopyDocs() {
rm -rf "$fish_site/site/docs/$1"
cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
git -C "$fish_site" add "site/docs/$1"
cp -r "$tmpdir/fish-$version/user_doc/html" "$fish_site/site/docs/$1"
git -C $fish_site add "site/docs/$1"
}
minor_version=${version%.*}
CopyDocs "$minor_version"
latest_release=$(
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
sed '
# De-prioritize release candidates (1.2.3-rc0)
s/-/~/g
' | LC_ALL=C sort --version-sort
)
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
's/-/~/g' | LC_ALL=C sort --version-sort)
printf %s\\n "$releases" | tail -1
)
if [ "$version" = "$latest_release" ]; then
@@ -255,28 +223,6 @@ do
sleep 20
done
milestone_version="$(
if echo "$version" | grep -q '\.0$'; then
echo "$minor_version"
else
echo "$version"
fi
)"
milestone_number() {
gh_api_repo milestones?state=open |
jq --arg name "fish $1" '
.[] | select(.title == $name) | .number
'
}
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
--method PATCH --raw-field state=closed
next_minor_version=$(echo "$minor_version" |
awk -F. '{ printf "%s.%s", $1, $2+1 }')
if [ -z "$(milestone_number "$next_minor_version")" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_minor_version"
fi
(
cd "$fish_site"
make new-release
@@ -293,12 +239,10 @@ fi
# This takes care to support remote names that are different from
# fish-shell remote name. Also, support detached HEAD state.
git push "$fish_site_repo" HEAD:master
git fetch "$fish_site_repo" \
"$(git rev-parse HEAD):refs/remotes/origin/master"
)
if [ -n "$integration_branch" ]; then {
git push "$remote" "$version^{commit}:refs/heads/$integration_branch"
git push $remote "$version^{commit}":refs/heads/$integration_branch
} else {
changelog=$(cat - CHANGELOG.rst <<EOF
fish ?.?.? (released ???)
@@ -307,11 +251,33 @@ fish ?.?.? (released ???)
EOF
)
printf %s\\n "$changelog" >CHANGELOG.rst
git add CHANGELOG.rst
CreateCommit "start new cycle"
git push "$remote" HEAD:master
CommitVersion ${version}-snapshot "start new cycle"
git push $remote HEAD:master
} fi
milestone_version="$(
if echo "$version" | grep -q '\.0$'; then
echo "$minor_version"
else
echo "$version"
fi
)"
milestone_number=$(
gh_api_repo milestones?state=open |
jq --arg name "fish $1" '
.[] | select(.title == $name) | .number
'
)
gh_api_repo milestones/"$milestone_number" --method PATCH \
--raw-field state=closed
next_minor_version=$(echo "$minor_version" |
awk -F. '{ printf "%s.%s", $1, $2+1 }')
if [ -z "$(milestone_number "$next_minor_version")" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_minor_version"
fi
exit
}

123
build_tools/style.fish Executable file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env fish
#
# This runs Python files, fish scripts (*.fish), and Rust files
# through their respective code formatting programs.
#
# `--all`: Format all eligible files instead of the ones specified as arguments.
# `--check`: Instead of reformatting, fail if a file is not formatted correctly.
# `--force`: Proceed without asking if uncommitted changes are detected.
# Only relevant if `--all` is specified but `--check` is not specified.
set -l fish_files
set -l python_files
set -l rust_files
set -l all no
argparse all check force -- $argv
or exit $status
if set -l -q _flag_all
set all yes
if set -q argv[1]
echo "Unexpected arguments: '$argv'"
exit 1
end
end
set -l workspace_root (status dirname)/..
if test $all = yes
if not set -l -q _flag_force; and not set -l -q _flag_check
# Potential for false positives: Not all fish files are formatted, see the `fish_files`
# definition below.
set -l relevant_uncommitted_changes (git status --porcelain --short --untracked-files=all | sed -e 's/^ *[^ ]* *//' | grep -E '.*\.(fish|py|rs)$')
if set -q relevant_uncommitted_changes[1]
for changed_file in $relevant_uncommitted_changes
echo $changed_file
end
echo
echo 'You have uncommitted changes (listed above). Are you sure you want to restyle?'
read -P 'y/N? ' -n1 -l ans
if not string match -qi y -- $ans
exit 1
end
end
end
set fish_files $workspace_root/{benchmarks,build_tools,etc,share}/**.fish
set python_files $workspace_root
else
# Format the files specified as arguments.
set -l files $argv
set fish_files (string match -r '^.*\.fish$' -- $files)
set python_files (string match -r '^.*\.py$' -- $files)
set rust_files (string match -r '^.*\.rs$' -- $files)
end
set -l red (set_color red)
set -l green (set_color green)
set -l yellow (set_color yellow)
set -l normal (set_color normal)
function die -V red -V normal
echo $red$argv[1]$normal
exit 1
end
if set -q fish_files[1]
if not type -q fish_indent
echo
echo $yellow'Could not find `fish_indent` in `$PATH`.'$normal
exit 127
end
echo === Running "$green"fish_indent"$normal"
if set -l -q _flag_check
fish_indent --check -- $fish_files
or die "Fish files are not formatted correctly."
else
fish_indent -w -- $fish_files
end
end
if set -q python_files[1]
if not type -q ruff
echo
echo $yellow'Please install `ruff` to style python'$normal
exit 127
end
echo === Running "$green"ruff format"$normal"
if set -l -q _flag_check
ruff format --check $python_files
or die "Python files are not formatted correctly."
else
ruff format $python_files
end
end
if test $all = yes; or set -q rust_files[1]
if not cargo fmt --version >/dev/null
echo
echo $yellow'Please install "rustfmt" to style Rust, e.g. via:'
echo "rustup component add rustfmt"$normal
exit 127
end
set -l edition_spec string match -r '^edition\s*=.*'
test "$($edition_spec <Cargo.toml)" = "$($edition_spec <.rustfmt.toml)"
or die "Cargo.toml and .rustfmt.toml use different editions"
echo === Running "$green"rustfmt"$normal"
if set -l -q _flag_check
if test $all = yes
cargo fmt --all --check
else
rustfmt --check --files-with-diff $rust_files
end
or die "Rust files are not formatted correctly."
else
if test $all = yes
cargo fmt --all
else
rustfmt $rust_files
end
end
end

View File

@@ -1,22 +0,0 @@
# /// script
# requires-python = ">=3.5"
# dependencies = [
# "launchpadlib",
# ]
# ///
from launchpadlib.launchpad import Launchpad
if __name__ == "__main__":
launchpad = Launchpad.login_anonymously(
"fish shell build script", "production", "~/.cache", version="devel"
)
ubu = launchpad.projects("ubuntu")
print(
"\n".join(
x["name"]
for x in ubu.series.entries
if x["supported"] == True
and x["name"] not in ("trusty", "xenial", "bionic", "focal")
)
)

View File

@@ -3,58 +3,29 @@
set -ex
command -v curl
command -v gcloud
command -v jq
command -v rustup
command -v updatecli
command -v uv
sort --version-sort </dev/null
# TODO This is copied from .github/actions/install-sphinx/action.yml
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
update_gh_action() {
repo=$1
version=$(curl -fsS "https://api.github.com/repos/$repo/releases/latest" | jq -r .tag_name)
[ -n "$version" ]
tag_oid=$(git ls-remote "https://github.com/$repo.git" "refs/tags/$version" | cut -f1)
[ -n "$tag_oid" ]
workflow_files=$(find .github/workflows -name '*.yml' -type f)
# shellcheck disable=2086
grep "\buses: $repo@\S\+\( \+#.*\)\?" $workflow_files
# shellcheck disable=2086
sed -i "s|\buses: $repo@\S\+\( \+#.*\)\?|\
uses: $repo@$tag_oid # $version, build_tools/update-dependencies.sh|g" \
$workflow_files
}
update_gh_action actions/checkout
update_gh_action actions/download-artifact
update_gh_action actions/github-script
update_gh_action actions/upload-artifact
update_gh_action dessant/lock-threads
update_gh_action EmbarkStudios/cargo-deny-action
update_gh_action msys2/setup-msys2
update_gh_action softprops/action-gh-release
uv lock --check
updatecli "${@:-apply}"
# Python version constraints may have changed.
uv lock --upgrade --exclude-newer="$(date --date='7 days ago' --iso-8601)"
uv lock # Python version constraints may have changed.
uv lock --upgrade
from_gh() {
repo=$1
branch=$2
path=$3
destination=$4
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/"${branch}"/"${path}")
printf '%s\n' "$contents" >"$destination"
path=$2
out_dir=$3
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/master/"${path}")
printf '%s\n' >"$out_dir/$(basename "$path")" "$contents"
}
from_gh ridiculousfish/widecharwidth master widechar_width.rs crates/widecharwidth/src/widechar_width.rs
from_gh ridiculousfish/littlecheck master littlecheck/littlecheck.py tests/littlecheck.py
from_gh catppuccin/fish main themes/catppuccin-frappe.theme share/themes/catppuccin-frappe.theme
from_gh catppuccin/fish main themes/catppuccin-macchiato.theme share/themes/catppuccin-macchiato.theme
from_gh catppuccin/fish main themes/catppuccin-mocha.theme share/themes/catppuccin-mocha.theme
from_gh ridiculousfish/widecharwidth widechar_width.rs src/widecharwidth/
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/
# Update Cargo.lock
cargo update

View File

@@ -0,0 +1,154 @@
#!/usr/bin/env fish
# Updates the files used for gettext translations.
# By default, the whole xgettext + msgmerge pipeline runs,
# which extracts the messages from the source files into $template_file,
# and updates the PO files for each language from that.
#
# Use cases:
# For developers:
# - Run with no args to update all PO files after making changes to Rust/fish sources.
# For translators:
# - Specify the language you want to work on as an argument, which must be a file in the po/
# directory. You can specify a language which does not have translations yet by specifying the
# name of a file which does not yet exist. Make sure to follow the naming convention.
# For testing:
# - Specify `--dry-run` to see if any updates to the PO files would by applied by this script.
# If this flag is specified, the script will exit with an error if there are outstanding
# changes, and will display the diff. Do not specify other flags if `--dry-run` is specified.
#
# Specify `--use-existing-template=FILE` to prevent running cargo for extracting an up-to-date
# version of the localized strings. This flag is intended for testing setups which make it
# inconvenient to run cargo here, but run it in an earlier step to ensure up-to-date values.
# This argument is passed on to the `fish_xgettext.fish` script and has no other uses.
# `FILE` must be the path to a gettext template file generated from our compilation process.
# It can be obtained by running:
# set -l FILE (mktemp)
# FISH_GETTEXT_EXTRACTION_FILE=$FILE cargo check --features=gettext-extract
# The sort utility is locale-sensitive.
# Ensure that sorting output is consistent by setting LC_ALL here.
set -gx LC_ALL C.UTF-8
set -l build_tools (status dirname)
set -l po_dir $build_tools/../po
set -l extract
argparse dry-run use-existing-template= -- $argv
or exit $status
if test -z $argv[1]
# Update everything if not specified otherwise.
set -g po_files $po_dir/*.po
else
set -l po_dir_id (stat --format='%d:%i' -- $po_dir)
for arg in $argv
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg) 2>/dev/null)
if test $po_dir_id != "$arg_dir_id"
echo "Argument $arg is not a file in the directory $(realpath $po_dir)."
echo "Non-option arguments must specify paths to files in this directory."
echo ""
echo "If you want to add a new language to the translations not the following:"
echo "The filename must identify a language, with a two letter ISO 639-1 language code of the target language (e.g. 'pt' for Portuguese), and use the file extension '.po'."
echo "Optionally, you can specify a regional variant (e.g. 'pt_BR')."
echo "So valid filenames are of the shape 'll.po' or 'll_CC.po'."
exit 1
end
if not basename $arg | grep -qE '^[a-z]{2,3}(_[A-Z]{2})?\.po$'
echo "Filename does not match the expected format ('ll.po' or 'll_CC.po')."
exit 1
end
end
set -g po_files $argv
end
set -g template_file (mktemp)
# Protect from externally set $tmpdir leaking into this script.
set -g tmpdir
function cleanup_exit
set -l exit_status $status
rm $template_file
if set -g --query tmpdir[1]
rm -r $tmpdir
end
exit $exit_status
end
if set -l --query extract
set -l xgettext_args
if set -l --query _flag_use_existing_template
set xgettext_args --use-existing-template=$_flag_use_existing_template
end
$build_tools/fish_xgettext.fish $xgettext_args >$template_file
or cleanup_exit
end
if set -l --query _flag_dry_run
# On a dry run, we do not modify po/ but write to a temporary directory instead and check if
# there is a difference between po/ and the tmpdir after re-generating the PO files.
set -g tmpdir (mktemp -d)
# Ensure tmpdir has the same initial state as the po dir.
cp -r $po_dir/* $tmpdir
end
# This is used to identify lines which should be set here via $header_lines.
# Make sure that this prefix does not appear elsewhere in the file and only contains characters
# without special meaning in a sed pattern.
set -g header_prefix "# fish-note-sections: "
function print_header
set -l header_lines \
"Translations are divided into sections, each starting with a fish-section-* pseudo-message." \
"The first few sections are more important." \
"Ignore the tier3 sections unless you have a lot of time."
for line in $header_lines
printf '%s%s\n' $header_prefix $line
end
end
function merge_po_files --argument-names template_file po_file
msgmerge --no-wrap --update --no-fuzzy-matching --backup=none --quiet \
$po_file $template_file
or cleanup_exit
set -l new_po_file (mktemp) # TODO Remove on failure.
# Remove obsolete messages instead of keeping them as #~ entries.
and msgattrib --no-wrap --no-obsolete -o $new_po_file $po_file
or cleanup_exit
begin
print_header
# Paste PO file without old header lines.
sed '/^'$header_prefix'/d' $new_po_file
end >$po_file
rm $new_po_file
end
for po_file in $po_files
if set --query tmpdir[1]
set po_file $tmpdir/(basename $po_file)
end
if test -e $po_file
merge_po_files $template_file $po_file
else
begin
print_header
cat $template_file
end >$po_file
end
end
if set -g --query tmpdir[1]
diff -ur $po_dir $tmpdir
or begin
echo ERROR: translations in ./po/ are stale. Try running build_tools/update_translations.fish
cleanup_exit
end
end
cleanup_exit

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
set -euo pipefail
@@ -11,6 +11,6 @@ codename=$(
curl -fsS https://sources.debian.org/api/src/"${package}"/ |
jq -r --arg codename "${codename}" '
.versions[] | select(.suites[] == $codename) | .version' |
sed -E 's/^([0-9]+\.[0-9]+).*/\1/' |
sed 's/^\([0-9]\+\.[0-9]\+\).*/\1/' |
sort --version-sort |
tail -1

View File

@@ -1,6 +0,0 @@
allow-print-in-tests = true
disallowed-methods = [
# Not allowed to use libc::setlocale() directly, need to use the wrappers
# in src/locale.rs which takes a lock for the duration of the call.
"libc::setlocale",
]

View File

@@ -1,60 +1,88 @@
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
HINTS
$ENV{SPHINX_DIR}
PATH_SUFFIXES bin
DOC "Sphinx documentation generator")
include(FeatureSummary)
set(SPHINX_OUTPUT_DIR "${FISH_RUST_BUILD_DIR}/fish-docs")
set(FISH_INDENT_FOR_BUILDING_DOCS "" CACHE FILEPATH "Path to fish_indent executable for building HTML docs")
if(FISH_INDENT_FOR_BUILDING_DOCS)
set(SPHINX_HTML_FISH_INDENT_DEP)
else()
set(FISH_INDENT_FOR_BUILDING_DOCS "${CMAKE_CURRENT_BINARY_DIR}/fish_indent")
set(SPHINX_HTML_FISH_INDENT_DEP fish_indent)
endif()
set(VARS_FOR_CARGO_SPHINX_WRAPPER
"CARGO_TARGET_DIR=${FISH_RUST_BUILD_DIR}"
"FISH_SPHINX=${SPHINX_EXECUTABLE}"
)
set(SPHINX_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/doc_src")
set(SPHINX_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/user_doc")
set(SPHINX_BUILD_DIR "${SPHINX_ROOT_DIR}/build")
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
# sphinx-docs uses fish_indent for highlighting.
# Prepend the output dir of fish_indent to PATH.
add_custom_target(sphinx-docs
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
${Rust_CARGO} xtask html-docs --fish-indent=${FISH_INDENT_FOR_BUILDING_DOCS}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS ${SPHINX_HTML_FISH_INDENT_DEP}
mkdir -p ${SPHINX_HTML_DIR}/_static/
COMMAND env PATH="${CMAKE_BINARY_DIR}:$$PATH"
${SPHINX_EXECUTABLE}
-j auto
-q -b html
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
"${SPHINX_SRC_DIR}"
"${SPHINX_HTML_DIR}"
DEPENDS ${SPHINX_SRC_DIR}/fish_indent_lexer.py fish_indent
COMMENT "Building HTML documentation with Sphinx")
# sphinx-manpages needs the fish_indent binary for the version number
add_custom_target(sphinx-manpages
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
${Rust_CARGO} xtask man-pages
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
env FISH_BUILD_VERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/${FBVF}
${SPHINX_EXECUTABLE}
-j auto
-q -b man
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_ROOT_DIR}/.doctrees-man"
"${SPHINX_SRC_DIR}"
# TODO: This only works if we only have section 1 manpages.
"${SPHINX_MANPAGE_DIR}/man1"
DEPENDS CHECK-FISH-BUILD-VERSION-FILE
COMMENT "Building man pages with Sphinx")
if(NOT DEFINED WITH_DOCS) # Don't check for legacy options if the new one is defined, to help bisecting.
if(DEFINED BUILD_DOCS)
message(FATAL_ERROR "the BUILD_DOCS option is no longer supported, use -DWITH_DOCS=ON|OFF")
endif()
if(DEFINED INSTALL_DOCS)
message(FATAL_ERROR "the INSTALL_DOCS option is no longer supported, use -DWITH_DOCS=ON|OFF")
endif()
endif()
if(SPHINX_EXECUTABLE)
option(WITH_DOCS "build documentation (requires Sphinx)" ON)
else()
option(WITH_DOCS "build documentation (requires Sphinx)" OFF)
endif()
option(BUILD_DOCS "build documentation (requires Sphinx)" ON)
else(SPHINX_EXECUTABLE)
option(BUILD_DOCS "build documentation (requires Sphinx)" OFF)
endif(SPHINX_EXECUTABLE)
if(WITH_DOCS AND NOT SPHINX_EXECUTABLE)
if(BUILD_DOCS AND NOT SPHINX_EXECUTABLE)
message(FATAL_ERROR "build documentation selected, but sphinx-build could not be found")
endif()
add_feature_info(Documentation WITH_DOCS "user manual and documentation")
if(WITH_DOCS)
add_custom_target(doc ALL DEPENDS sphinx-docs sphinx-manpages)
# Group docs targets into a DocsTargets folder
set_property(
TARGET doc sphinx-docs sphinx-manpages
PROPERTY FOLDER cmake/DocTargets
)
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/user_doc/html
AND IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/user_doc/man)
set(HAVE_PREBUILT_DOCS TRUE)
else()
set(HAVE_PREBUILT_DOCS FALSE)
endif()
if(BUILD_DOCS OR HAVE_PREBUILT_DOCS)
set(INSTALL_DOCS ON)
else()
set(INSTALL_DOCS OFF)
endif()
add_feature_info(Documentation INSTALL_DOCS "user manual and documentation")
set(USE_PREBUILT_DOCS FALSE)
if(BUILD_DOCS)
configure_file("${SPHINX_SRC_DIR}/conf.py" "${SPHINX_BUILD_DIR}/conf.py" @ONLY)
add_custom_target(doc ALL
DEPENDS sphinx-docs sphinx-manpages)
# Group docs targets into a DocsTargets folder
set_property(TARGET doc sphinx-docs sphinx-manpages
PROPERTY FOLDER cmake/DocTargets)
elseif(HAVE_PREBUILT_DOCS)
set(USE_PREBUILT_DOCS TRUE)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
# Out of tree build - link the prebuilt documentation to the build tree
add_custom_target(link_doc ALL)
add_custom_command(TARGET link_doc
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/user_doc ${CMAKE_CURRENT_BINARY_DIR}/user_doc
POST_BUILD)
endif()
endif(BUILD_DOCS)

View File

@@ -22,17 +22,17 @@ foreach(_VAR ${_Rust_USER_VARS})
endforeach()
if (NOT DEFINED Rust_CARGO_CACHED)
find_program(Rust_CARGO_CACHED cargo PATHS "$ENV{HOME}/.cargo/bin")
find_program(Rust_CARGO_CACHED cargo PATHS "$ENV{HOME}/.cargo/bin")
endif()
if (NOT EXISTS "${Rust_CARGO_CACHED}")
message(FATAL_ERROR "The cargo executable ${Rust_CARGO_CACHED} was not found. "
"Consider setting `Rust_CARGO_CACHED` to the absolute path of `cargo`."
)
message(FATAL_ERROR "The cargo executable ${Rust_CARGO_CACHED} was not found. "
"Consider setting `Rust_CARGO_CACHED` to the absolute path of `cargo`."
)
endif()
if (NOT DEFINED Rust_COMPILER_CACHED)
find_program(Rust_COMPILER_CACHED rustc PATHS "$ENV{HOME}/.cargo/bin")
find_program(Rust_COMPILER_CACHED rustc PATHS "$ENV{HOME}/.cargo/bin")
endif()
@@ -45,31 +45,31 @@ endif()
# Figure out the target by just using the host target.
# If you want to cross-compile, you'll have to set Rust_CARGO_TARGET
if(NOT Rust_CARGO_TARGET_CACHED)
execute_process(
COMMAND "${Rust_COMPILER_CACHED}" --version --verbose
OUTPUT_VARIABLE _RUSTC_VERSION_RAW
RESULT_VARIABLE _RUSTC_VERSION_RESULT
execute_process(
COMMAND "${Rust_COMPILER_CACHED}" --version --verbose
OUTPUT_VARIABLE _RUSTC_VERSION_RAW
RESULT_VARIABLE _RUSTC_VERSION_RESULT
)
if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" ))
message(FATAL_ERROR "Failed to get rustc version.\n"
"${Rust_COMPILER} --version failed with error: `${_RUSTC_VERSION_RESULT}`")
endif()
if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR
"Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}"
)
endif()
if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" ))
message(FATAL_ERROR "Failed to get rustc version.\n"
"${Rust_COMPILER} --version failed with error: `${_RUSTC_VERSION_RESULT}`")
endif()
if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR
"Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}"
)
endif()
if(CMAKE_CROSSCOMPILING)
message(FATAL_ERROR "CMake is in cross-compiling mode."
"Manually set `Rust_CARGO_TARGET`."
)
endif()
set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple")
if(CMAKE_CROSSCOMPILING)
message(FATAL_ERROR "CMake is in cross-compiling mode."
"Manually set `Rust_CARGO_TARGET`."
)
endif()
set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple")
endif()
# Set the input variables as non-cache variables so that the variables are available after

View File

@@ -1,5 +1,7 @@
set(CMAKE_INSTALL_MESSAGE NEVER)
set(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/fish ${CMAKE_CURRENT_BINARY_DIR}/fish_indent ${CMAKE_CURRENT_BINARY_DIR}/fish_key_reader)
set(prefix ${CMAKE_INSTALL_PREFIX})
set(bindir ${CMAKE_INSTALL_BINDIR})
set(sysconfdir ${CMAKE_INSTALL_SYSCONFDIR})
@@ -14,36 +16,32 @@ set(rel_completionsdir "fish/vendor_completions.d")
set(rel_functionsdir "fish/vendor_functions.d")
set(rel_confdir "fish/vendor_conf.d")
set(
extra_completionsdir "${datadir}/${rel_completionsdir}"
CACHE STRING "Path for extra completions"
)
set(extra_completionsdir
"${datadir}/${rel_completionsdir}"
CACHE STRING "Path for extra completions")
set(
extra_functionsdir "${datadir}/${rel_functionsdir}"
CACHE STRING "Path for extra functions"
)
set(extra_functionsdir
"${datadir}/${rel_functionsdir}"
CACHE STRING "Path for extra functions")
set(
extra_confdir "${datadir}/${rel_confdir}"
CACHE STRING "Path for extra configuration"
)
set(extra_confdir
"${datadir}/${rel_confdir}"
CACHE STRING "Path for extra configuration")
# These are the man pages that go in system manpath; all manpages go in the fish-specific manpath.
set(MANUALS
${SPHINX_OUTPUT_DIR}/man/man1/fish.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_indent.1
${SPHINX_OUTPUT_DIR}/man/man1/fish_key_reader.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-doc.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-language.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-interactive.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-terminal-compatibility.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-completions.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-prompt-tutorial.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-for-bash-users.1
${SPHINX_OUTPUT_DIR}/man/man1/fish-faq.1
set(MANUALS ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_indent.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_key_reader.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-doc.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-tutorial.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-language.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-interactive.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-terminal-compatibility.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-completions.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-prompt-tutorial.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-for-bash-users.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-faq.1
)
# Determine which man page we don't want to install.
@@ -52,119 +50,118 @@ set(MANUALS
# On other operating systems, don't install a realpath man page, as they almost all have a realpath
# command, while macOS does not.
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CONDEMNED_PAGE "open.1")
set(CONDEMNED_PAGE "open.1")
else()
set(CONDEMNED_PAGE "realpath.1")
set(CONDEMNED_PAGE "realpath.1")
endif()
# Define a function to help us create directories.
function(FISH_CREATE_DIRS)
foreach(dir ${ARGV})
install(DIRECTORY DESTINATION ${dir})
endforeach(dir)
foreach(dir ${ARGV})
install(DIRECTORY DESTINATION ${dir})
endforeach(dir)
endfunction(FISH_CREATE_DIRS)
function(FISH_TRY_CREATE_DIRS)
foreach(dir ${ARGV})
if(NOT IS_ABSOLUTE ${dir})
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
else()
set(abs_dir "\$ENV{DESTDIR}${dir}")
endif()
install(SCRIPT CODE "
EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
")
endforeach()
foreach(dir ${ARGV})
if(NOT IS_ABSOLUTE ${dir})
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
else()
set(abs_dir "\$ENV{DESTDIR}${dir}")
endif()
install(SCRIPT CODE "EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
")
endforeach()
endfunction(FISH_TRY_CREATE_DIRS)
install(
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/fish
PERMISSIONS
OWNER_READ
OWNER_WRITE
OWNER_EXECUTE
GROUP_READ
GROUP_EXECUTE
WORLD_READ
WORLD_EXECUTE
DESTINATION ${bindir}
)
if(NOT IS_ABSOLUTE ${bindir})
set(abs_bindir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${bindir}")
else()
set(abs_bindir "\$ENV{DESTDIR}${bindir}")
endif()
install(CODE "file(CREATE_LINK ${abs_bindir}/fish ${abs_bindir}/fish_indent)")
install(CODE "file(CREATE_LINK ${abs_bindir}/fish ${abs_bindir}/fish_key_reader)")
install(PROGRAMS ${PROGRAMS}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ
GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
DESTINATION ${bindir})
fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
${sysconfdir}/fish/functions)
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
fish_create_dirs(
${rel_datadir}/fish
${rel_datadir}/fish/man/man1
)
fish_create_dirs(${rel_datadir}/fish ${rel_datadir}/fish/completions
${rel_datadir}/fish/functions ${rel_datadir}/fish/groff
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
${rel_datadir}/fish/tools/web_config
${rel_datadir}/fish/tools/web_config/js
${rel_datadir}/fish/tools/web_config/sample_prompts
${rel_datadir}/fish/tools/web_config/themes
)
# This file is embedded in the executable by rust-embed and never read from the filesystem
configure_file(share/__fish_build_paths.fish.in share/__fish_build_paths.fish)
install(FILES share/config.fish
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
DESTINATION ${rel_datadir}/fish)
# Create only the vendor directories inside the prefix (#5029 / #6508)
fish_create_dirs(
${rel_datadir}/fish/vendor_completions.d
${rel_datadir}/fish/vendor_functions.d
${rel_datadir}/fish/vendor_conf.d
)
fish_create_dirs(${rel_datadir}/fish/vendor_completions.d ${rel_datadir}/fish/vendor_functions.d
${rel_datadir}/fish/vendor_conf.d)
fish_try_create_dirs(${rel_datadir}/pkgconfig)
configure_file(fish.pc.in fish.pc.noversion @ONLY)
add_custom_command(
OUTPUT fish.pc
add_custom_command(OUTPUT fish.pc
COMMAND sed '/Version/d' fish.pc.noversion > fish.pc
COMMAND printf "Version: " >> fish.pc
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh >> fish.pc
COMMAND cat ${FBVF} >> fish.pc
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion
)
DEPENDS CHECK-FISH-BUILD-VERSION-FILE ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion)
add_custom_target(build_fish_pc ALL DEPENDS fish.pc)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
DESTINATION ${rel_datadir}/pkgconfig
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
DESTINATION ${rel_datadir}/pkgconfig)
install(DIRECTORY share/completions/
DESTINATION ${rel_datadir}/fish/completions
FILES_MATCHING PATTERN "*.fish")
install(DIRECTORY share/functions/
DESTINATION ${rel_datadir}/fish/functions
FILES_MATCHING PATTERN "*.fish")
install(DIRECTORY share/groff
DESTINATION ${rel_datadir}/fish)
# CONDEMNED_PAGE is managed by the conditional above
# Building the man pages is optional: if sphinx isn't installed, they're not built
install(
DIRECTORY ${SPHINX_OUTPUT_DIR}/man/man1/
DESTINATION ${rel_datadir}/fish/man/man1
FILES_MATCHING
PATTERN "*.1"
PATTERN ${CONDEMNED_PAGE} EXCLUDE
)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
DESTINATION ${rel_datadir}/fish/man/man1
FILES_MATCHING
PATTERN "*.1"
PATTERN ${CONDEMNED_PAGE} EXCLUDE)
install(PROGRAMS share/tools/create_manpage_completions.py
DESTINATION ${rel_datadir}/fish/tools/)
install(DIRECTORY share/tools/web_config
DESTINATION ${rel_datadir}/fish/tools/
FILES_MATCHING
PATTERN "*.png"
PATTERN "*.css"
PATTERN "*.html"
PATTERN "*.py"
PATTERN "*.js"
PATTERN "*.theme"
PATTERN "*.fish")
# Building the man pages is optional: if Sphinx isn't installed, they're not built
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
install(
DIRECTORY ${SPHINX_OUTPUT_DIR}/html/ # Trailing slash is important!
DESTINATION ${docdir} OPTIONAL
)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/html/ # Trailing slash is important!
DESTINATION ${docdir} OPTIONAL)
install(FILES CHANGELOG.rst DESTINATION ${docdir})
# Group install targets into a InstallTargets folder
set_property(
TARGET build_fish_pc
PROPERTY FOLDER cmake/InstallTargets
)
set_property(TARGET build_fish_pc CHECK-FISH-BUILD-VERSION-FILE
PROPERTY FOLDER cmake/InstallTargets)
# Make a target build_root that installs into the buildroot directory, for testing.
set(BUILDROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/buildroot)
add_custom_target(
build_root
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install
)
add_custom_target(build_root
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install)

View File

@@ -26,7 +26,7 @@ add_executable(fish_macapp EXCLUDE_FROM_ALL
# so cmake must be re-run after version changes for the app to be updated. But
# generally this will be run by make_macos_pkg.sh which always re-runs cmake.
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
COMMAND cut -d- -f1
OUTPUT_VARIABLE FISH_SHORT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)

View File

@@ -1,10 +1,9 @@
set(FISH_USE_SYSTEM_PCRE2 ON CACHE BOOL
"Try to use PCRE2 from the system, instead of the pcre2-sys version"
)
"Try to use PCRE2 from the system, instead of the pcre2-sys version")
if(FISH_USE_SYSTEM_PCRE2)
message(STATUS "Trying to use PCRE2 from the system")
message(STATUS "Trying to use PCRE2 from the system")
else()
message(STATUS "Forcing static build of PCRE2")
set(FISH_PCRE2_BUILDFLAG "PCRE2_SYS_STATIC=1")
message(STATUS "Forcing static build of PCRE2")
set(FISH_PCRE2_BUILDFLAG "PCRE2_SYS_STATIC=1")
endif(FISH_USE_SYSTEM_PCRE2)

View File

@@ -1,11 +1,13 @@
include(FeatureSummary)
include(FindRust)
find_package(Rust REQUIRED)
set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo")
set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo/build")
list(APPEND FISH_CARGO_FEATURES_LIST "embed-data")
if(DEFINED ASAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
list(APPEND FISH_CARGO_FEATURES_LIST "asan")
endif()
if(DEFINED TSAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
@@ -19,18 +21,12 @@ else()
endif()
set(rust_profile $<IF:$<CONFIG:Debug>,debug,$<IF:$<CONFIG:RelWithDebInfo>,release-with-debug,release>>)
set(rust_debugflags "$<$<CONFIG:Debug>:-g>$<$<CONFIG:RelWithDebInfo>:-g>")
if (NOT DEFINED WITH_MESSAGE_LOCALIZATION) # Don't check for legacy options if the new one is defined, to help bisecting.
if(DEFINED WITH_GETTEXT)
message(FATAL_ERROR "the WITH_GETTEXT option is no longer supported, use -DWITH_MESSAGE_LOCALIZATION=ON|OFF")
endif()
endif()
option(WITH_MESSAGE_LOCALIZATION "Build with localization support. Requires `msgfmt` to work." ON)
option(WITH_GETTEXT "Build with gettext localization support. Requires `msgfmt` to work." ON)
# Enable gettext feature unless explicitly disabled.
if(NOT DEFINED WITH_MESSAGE_LOCALIZATION OR "${WITH_MESSAGE_LOCALIZATION}")
if(NOT DEFINED WITH_GETTEXT OR "${WITH_GETTEXT}")
list(APPEND FISH_CARGO_FEATURES_LIST "localize-messages")
endif()
add_feature_info(Translation WITH_MESSAGE_LOCALIZATION "message localization (requires gettext)")
list(JOIN FISH_CARGO_FEATURES_LIST , FISH_CARGO_FEATURES)

View File

@@ -1,27 +1,29 @@
add_executable(fish_test_helper tests/fish_test_helper.c)
FILE(GLOB FISH_CHECKS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/checks/*.fish)
foreach(CHECK ${FISH_CHECKS})
get_filename_component(CHECK_NAME ${CHECK} NAME)
add_custom_target(
test_${CHECK_NAME}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK_NAME}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
get_filename_component(CHECK_NAME ${CHECK} NAME)
add_custom_target(
test_${CHECK_NAME}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK_NAME}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader fish_test_helper
USES_TERMINAL
)
endforeach(CHECK)
FILE(GLOB PEXPECTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/pexpects/*.py)
foreach(PEXPECT ${PEXPECTS})
get_filename_component(PEXPECT ${PEXPECT} NAME)
add_custom_target(
test_${PEXPECT}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
)
get_filename_component(PEXPECT ${PEXPECT} NAME)
add_custom_target(
test_${PEXPECT}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader fish_test_helper
USES_TERMINAL
)
endforeach(PEXPECT)
# Rust stuff.
@@ -30,20 +32,14 @@ if(DEFINED ASAN)
# Rust w/ -Zsanitizer=address requires explicitly specifying the --target triple or else linker
# errors pertaining to asan symbols will ensue.
if(NOT DEFINED Rust_CARGO_TARGET)
message(
FATAL_ERROR
"ASAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple"
)
message(FATAL_ERROR "ASAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple")
endif()
endif()
if(DEFINED TSAN)
if(NOT DEFINED Rust_CARGO_TARGET)
message(
FATAL_ERROR
"TSAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple"
)
message(FATAL_ERROR "TSAN requires defining the CMake variable Rust_CARGO_TARGET to the
intended target triple")
endif()
endif()
@@ -59,18 +55,10 @@ endif()
# The top-level test target is "fish_run_tests".
add_custom_target(fish_run_tests
# TODO: This should be replaced with a unified solution, possibly build_tools/check.sh.
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${max_concurrency_flag} ${CMAKE_CURRENT_BINARY_DIR}
COMMAND
env ${VARS_FOR_CARGO} ${Rust_CARGO}
test
--no-default-features
--features=${FISH_CARGO_FEATURES}
${CARGO_FLAGS}
--workspace
--target-dir ${rust_target_dir}
${cargo_test_flags}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS fish fish_indent fish_key_reader
USES_TERMINAL
# TODO: This should be replaced with a unified solution, possibly build_tools/check.sh.
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${max_concurrency_flag} ${CMAKE_CURRENT_BINARY_DIR}
COMMAND env ${VARS_FOR_CARGO} ${Rust_CARGO} test --no-default-features ${CARGO_FLAGS} --workspace --target-dir ${rust_target_dir} ${cargo_test_flags}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
DEPENDS fish fish_indent fish_key_reader fish_test_helper
USES_TERMINAL
)

55
cmake/Version.cmake Normal file
View File

@@ -0,0 +1,55 @@
# This file adds commands to manage the FISH-BUILD-VERSION-FILE (hereafter
# FBVF). This file exists in the build directory and is used to populate the
# documentation and also the version string in fish_version.o (printed with
# `echo $version` and also fish --version). The essential idea is that we are
# going to invoke git_version_gen.sh, which will update the
# FISH-BUILD-VERSION-FILE only if it needs to change; this is what makes
# incremental rebuilds fast.
#
# This code is delicate, with the chief subtlety revolving around Ninja. A
# natural and naive approach would tell the generated build system that FBVF is
# a dependency of fish_version.o, and that git_version_gen.sh updates it. Make
# will then invoke the script, check the timestamp on fish_version.o and FBVF,
# see that FBVF is earlier, and then not rebuild fish_version.o. Ninja,
# however, decides what to build up-front and will unconditionally rebuild
# fish_version.o.
#
# To avoid this with Ninja, we want to hook into its 'restat' option which we
# can do through the BYPRODUCTS feature of CMake. See
# https://cmake.org/cmake/help/latest/policy/CMP0058.html
#
# Unfortunately BYPRODUCTS behaves strangely with the Makefile generator: it
# marks FBVF as generated and then CMake itself will `touch` it on every build,
# meaning that using BYPRODUCTS will cause fish_version.o to be rebuilt
# unconditionally with the Makefile generator. Thus we want to use the
# natural-and-naive approach for Makefiles.
# **IMPORTANT** If you touch these build rules, please test both Ninja and
# Makefile generators with both a clean and dirty git tree. Verify that both
# generated build systems rebuild fish when the git tree goes from dirty to
# clean (and vice versa), and verify they do NOT rebuild it when the git tree
# stays the same (incremental builds must be fast).
# Just a handy abbreviation.
set(FBVF FISH-BUILD-VERSION-FILE)
# TODO: find a cleaner way to do this.
IF (${CMAKE_GENERATOR} STREQUAL Ninja)
set(FBVF-OUTPUT fish-build-version-witness.txt)
set(CFBVF-BYPRODUCTS ${FBVF})
else(${CMAKE_GENERATOR} STREQUAL Ninja)
set(FBVF-OUTPUT ${FBVF})
set(CFBVF-BYPRODUCTS)
endif(${CMAKE_GENERATOR} STREQUAL Ninja)
# Set up the version targets
add_custom_target(CHECK-FISH-BUILD-VERSION-FILE
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS ${CFBVF-BYPRODUCTS})
add_custom_command(OUTPUT ${FBVF-OUTPUT}
DEPENDS CHECK-FISH-BUILD-VERSION-FILE)
# Abbreviation for the target.
set(CFBVF CHECK-FISH-BUILD-VERSION-FILE)

View File

@@ -1,536 +0,0 @@
fish (4.8.0-1) stable; urgency=medium
* Release of new version 4.8.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.8.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Wed, 24 Jun 2026 11:59:34 +0200
fish (4.7.1-1) stable; urgency=medium
* Release of new version 4.7.1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.1 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Fri, 08 May 2026 00:02:14 +0800
fish (4.7.0-1) stable; urgency=medium
* Release of new version 4.7.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 05 May 2026 15:24:27 +0800
fish (4.6.0-1) stable; urgency=medium
* Release of new version 4.6.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.6.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Sat, 28 Mar 2026 12:56:37 +0800
fish (4.5.0-1) stable; urgency=medium
* Release of new version 4.5.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.5.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 17 Feb 2026 11:32:33 +1100
fish (4.4.0-1) stable; urgency=medium
* Release of new version 4.4.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.4.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 03 Feb 2026 12:11:51 +1100
fish (4.3.3-1) stable; urgency=medium
* Release of new version 4.3.3.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.3 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Wed, 07 Jan 2026 08:34:20 +0100
fish (4.3.2-1) stable; urgency=medium
* Release of new version 4.3.2.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.2 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 30 Dec 2025 17:21:04 +0100
fish (4.3.1-1) stable; urgency=medium
* Release of new version 4.3.1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.1 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Sun, 28 Dec 2025 16:54:44 +0100
fish (4.3.0-1) stable; urgency=medium
* Release of new version 4.3.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.0 for details.
-- Johannes Altmanninger <aclopte@gmail.com> Sun, 28 Dec 2025 10:20:47 +0100
fish (4.2.1-1) testing; urgency=medium
* Release of new version 4.2.1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.2.1 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 13 Nov 2025 20:42:43 +0800
fish (4.2.0-1) testing; urgency=medium
* Release of new version 4.2.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.2.0 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 10 Nov 2025 19:29:03 +0800
fish (4.1.2-1) testing; urgency=medium
* Release of new version 4.1.2.
See https://github.com/fish-shell/fish-shell/releases/tag/4.1.2 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 08 Oct 2025 13:46:45 +0800
fish (4.1.1-1) testing; urgency=medium
* Release of new version 4.1.1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.1.1 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Fri, 03 Oct 2025 16:43:43 +0800
fish (4.0.8-1) testing; urgency=medium
* Release of new version 4.0.8.
See https://github.com/fish-shell/fish-shell/releases/tag/4.0.8 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 18 Sep 2025 22:17:43 +0800
fish (4.0.6-1) testing; urgency=medium
* Release of new version 4.0.6.
See https://github.com/fish-shell/fish-shell/releases/tag/4.0.6 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 17 Sep 2025 12:27:09 +0800
fish (4.0.2-2) testing; urgency=medium
* Fix tests on Debian.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 20 Apr 2025 23:08:14 +0800
fish (4.0.2-1) testing; urgency=medium
* Release of new version 4.0.2.
See https://github.com/fish-shell/fish-shell/releases/tag/4.0.2 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 20 Apr 2025 21:24:18 +0800
fish (4.0.1-1) testing; urgency=medium
* Release of new version 4.0.1.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 13 Mar 2025 11:30:21 +0800
fish (4.0.0-2) testing; urgency=medium
* Fix tests on Debian.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 27 Feb 2025 21:50:33 +0800
fish (4.0.0-1) testing; urgency=medium
* Release of new version 4.0.0.
See https://github.com/fish-shell/fish-shell/releases/tag/4.0.0 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 27 Feb 2025 19:22:30 +0800
fish (4.0.1-1) testing; urgency=medium
* Release of new beta version 4.0b1.
See https://github.com/fish-shell/fish-shell/releases/tag/4.0b1 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 17 Dec 2024 23:42:25 +0800
fish (3.7.1-1) testing; urgency=medium
* Release of new version 3.7.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.7.1 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 19 Mar 2024 13:26:22 +0800
fish (3.7.0-1) testing; urgency=medium
* Release of new version 3.7.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.7.0 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 01 Jan 2024 23:32:55 +0800
fish (3.6.4-1) testing; urgency=medium
* Release of new version 3.6.4.
See https://github.com/fish-shell/fish-shell/releases/tag/3.6.4 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 05 Dec 2023 22:34:09 +0800
fish (3.6.3-1) testing; urgency=medium
* Release of new version 3.6.3.
See https://github.com/fish-shell/fish-shell/releases/tag/3.6.3 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 05 Dec 2023 00:11:12 +0800
fish (3.6.2-1) testing; urgency=medium
* Release of new version 3.6.2.
* Includes a fix for CVE-2023-49284.
See https://github.com/fish-shell/fish-shell/releases/tag/3.6.2 for details.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 04 Dec 2023 23:16:42 +0800
fish (3.6.1-1) testing; urgency=medium
* Release of new version 3.6.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.6.1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 25 Mar 2023 17:22:12 +0800
fish (3.6.0-1) testing; urgency=medium
* Release of new version 3.6.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.6.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 07 Jan 2023 22:41:32 +0800
fish (3.5.1-1) testing; urgency=medium
* Release of new version 3.5.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.5.1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 20 Jul 2022 21:54:09 +0800
fish (3.5.0-1) testing; urgency=medium
* Release of new version 3.5.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.5.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 16 Jun 2022 19:45:33 +0800
fish (3.4.0-1) testing; urgency=medium
* Release of new version 3.4.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.4.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 12 Mar 2022 23:24:22 +0800
fish (3.3.1-1) testing; urgency=medium
* Release of new version 3.3.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.3.1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 06 Jul 2021 23:22:36 +0800
fish (3.3.0-1) testing; urgency=medium
* Release of new version 3.3.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.3.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 28 Jun 2021 23:06:36 +0800
fish (3.2.2-1) testing; urgency=medium
* Release of new version 3.2.2.
See https://github.com/fish-shell/fish-shell/releases/tag/3.2.2 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 07 Apr 2021 21:10:54 +0800
fish (3.2.1-1) testing; urgency=medium
* Release of new version 3.2.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.2.1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 18 Mar 2021 12:08:13 +0800
fish (3.2.0-1) testing; urgency=medium
* Release of new version 3.2.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.2.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 01 Mar 2021 21:22:39 +0800
fish (3.1.2-1) testing; urgency=medium
* Release of new version 3.1.2.
See https://github.com/fish-shell/fish-shell/releases/tag/3.1.2 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 29 Apr 2020 11:22:35 +0800
fish (3.1.1-1) testing; urgency=medium
* Release of new version 3.1.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.1.1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 27 Apr 2020 22:45:35 +0800
fish (3.1.0-1) testing; urgency=medium
* Release of new version 3.1.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.1.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 12 Feb 2020 22:34:53 +0800
fish (3.1.1-1) testing; urgency=medium
* Release of new beta version 3.1b1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.1b1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 26 Jan 2020 21:42:46 +0800
fish (3.0.2-1) testing; urgency=medium
* Release of new version 3.0.2.
See https://github.com/fish-shell/fish-shell/releases/tag/3.0.2 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 19 Feb 2019 21:45:05 +0800
fish (3.0.1-1) testing; urgency=medium
* Release of new version 3.0.1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.0.1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 11 Feb 2019 20:23:55 +0800
fish (3.0.0-1) testing; urgency=medium
* Release of new version 3.0.0.
See https://github.com/fish-shell/fish-shell/releases/tag/3.0.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Fri, 28 Dec 2018 21:10:28 +0800
fish (3.0.1-1) testing; urgency=medium
* Release of new beta version 3.0b1.
See https://github.com/fish-shell/fish-shell/releases/tag/3.0b1 for
significant changes, which includes backward incompatibility.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 11 Dec 2018 22:59:15 +0800
fish (2.7.1-1) testing; urgency=medium
* Release of new bug fix version 2.7.1. On all Linux platforms, this is
release will behave identically to 2.7.0.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 23 Dec 2017 00:43:12 +0800
fish (2.7.0-1) testing; urgency=medium
* Release of new version 2.7.0.
See https://github.com/fish-shell/fish-shell/releases/tag/2.7.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 23 Nov 2017 18:38:21 +0800
fish (2.7.1-1) testing; urgency=medium
* Release of new beta version 2.7b1.
See https://github.com/fish-shell/fish-shell/releases/tag/2.7b1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 31 Oct 2017 20:32:29 +0800
fish (2.6.0-1) testing; urgency=medium
* Relase of new version 2.6.0.
See https://github.com/fish-shell/fish-shell/releases/tag/2.6.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 03 Jun 2017 20:51:50 +0800
fish (2.6b1-1) testing; urgency=medium
* Release of new beta version 2.6b1.
See https://github.com/fish-shell/fish-shell/releases/tag/2.6b1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 14 May 2017 10:47:39 +0800
fish (2.5.0-1) testing; urgency=medium
* Release of new version 2.5.0.
See https://github.com/fish-shell/fish-shell/releases/tag/2.5.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Fri, 03 Feb 2017 09:52:57 +0800
fish (2.5b1-1) testing; urgency=medium
* Release of new beta version 2.5b1.
See https://github.com/fish-shell/fish-shell/releases/tag/2.5b1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 14 Jan 2017 08:49:34 +0800
fish (2.4.0-2) testing; urgency=medium
* Change recommendation of xdg-utils to suggestion (closes
https://github.com/fish-shell/fish-shell/issues/3534).
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Wed, 09 Nov 2016 22:56:16 +0800
fish (2.4.0-1) testing; urgency=medium
* Release of new version 2.4.0.
See https://github.com/fish-shell/fish-shell/releases/tag/2.4.0 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 08 Nov 2016 11:29:57 +0800
fish (2.4b1-1) testing; urgency=medium
* Release of new beta version 2.4b1.
See https://github.com/fish-shell/fish-shell/releases/tag/2.4b1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 18 Oct 2016 22:25:26 +0800
fish (2.3.1-1) testing; urgency=medium
* Release of new version 2.3.1.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 03 Jul 2016 21:21:51 +0800
fish (2.3.0-1) testing; urgency=medium
* Release of new version 2.3.0.
See http://fishshell.com/release_notes.html for significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 21 May 2016 06:59:54 +0800
fish (2.3b2-1) testing; urgency=medium
* Release of new beta version 2.3b2.
See https://github.com/fish-shell/fish-shell/releases/tag/2.3b2 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 05 May 2016 06:22:37 +0800
fish (2.3.1-1) testing; urgency=medium
* Release of new beta version 2.3b1.
See http://github.com/fish-shell/fish-shell/releases/tag/2.3b1 for
significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Tue, 19 Apr 2016 21:07:17 +0800
fish (2.2.0-2) testing; urgency=medium
* Binary rebuild only to resynchronise repository state.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Mon, 15 Feb 2016 22:45:05 +0800
fish (2.2.0-1) testing; urgency=medium
* Release of new version 2.2.0.
See http://fishshell.com/release_notes.html for significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 12 Jul 2015 18:52:29 +0800
fish (2.2b1-1) testing; urgency=low
* Release of new beta version 2.2b1.
See http://fishshell.com/staging/release_notes.html for significant
changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 07 May 2015 14:48:21 +0800
fish (2.1.1-1) unstable; urgency=high
* Release of new version 2.1.1.
See http://fishshell.com/release_notes.html for significant changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sun, 07 Sep 2014 17:06:31 +0800
fish (2.1.0-1) unstable; urgency=low
* Release of new version 2.1.0.
See http://fishshell.com/staging/release_notes.html for significant
changes.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Sat, 19 Oct 2013 14:35:23 +0800
fish (2.0.0-0) unstable; urgency=low
* Initial release of fish 2.0.0.
-- David Adam <zanchey@ucc.gu.uwa.edu.au> Thu, 19 Jul 2012 23:17:58 +0800

View File

@@ -1,3 +0,0 @@
# The vendor tarball drops a new version of .cargo/config into place. Representing this as a patch
# in automated workflows is tricky, so for our purposes auto-commit is fine.
auto-commit

View File

@@ -1,33 +0,0 @@
# Environment containing all dependencies needed for
# - building fish,
# - building documentation,
# - running all tests,
# - formatting and checking lints.
#
# enter interactive bash shell:
# nix-shell contrib/shell.nix
#
# using system nixpkgs (otherwise fetches pinned version):
# nix-shell contrib/shell.nix --arg pkgs 'import <nixpkgs> {}'
#
# run single command:
# nix-shell contrib/shell --run "cargo xtask check"
{ pkgs ? (import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.11.tar.gz";
sha256 = "1ia5kjykm9xmrpwbzhbaf4cpwi3yaxr7shl6amj8dajvgbyh2yh4";
}) { }), ... }:
pkgs.mkShell {
buildInputs = [
(pkgs.python3.withPackages (pyPkgs: [ pyPkgs.pexpect ]))
pkgs.cargo
pkgs.clippy
pkgs.cmake
pkgs.gettext
pkgs.pcre2
pkgs.procps # tests use pgrep/pkill
pkgs.ruff
pkgs.rustc
pkgs.rustfmt
pkgs.sphinx
];
}

View File

@@ -1,10 +1,9 @@
[package]
name = "fish-build-helper"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
rsconf.workspace = true

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt as _, path::Path};
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt, path::Path};
pub fn env_var(name: &str) -> Option<String> {
let err = match env::var(name) {
@@ -34,28 +34,6 @@ pub fn fish_build_dir() -> Cow<'static, Path> {
.unwrap_or(cargo_target_dir())
}
pub fn fish_doc_dir() -> Cow<'static, Path> {
cargo_target_dir().join("fish-docs").into()
}
fn l10n_dir() -> Cow<'static, Path> {
workspace_root().join("localization").into()
}
pub fn ftl_dir() -> Cow<'static, Path> {
l10n_dir().join("fluent").into()
}
pub static DEFAULT_LANGUAGE: &str = "en";
pub fn default_ftl_file() -> Cow<'static, Path> {
ftl_dir().join(format!("{DEFAULT_LANGUAGE}.ftl")).into()
}
pub fn po_dir() -> Cow<'static, Path> {
l10n_dir().join("po").into()
}
// TODO Move this to rsconf
pub fn rebuild_if_path_changed<P: AsRef<Path>>(path: P) {
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());
@@ -67,56 +45,3 @@ pub fn rebuild_if_paths_changed<P: AsRef<Path>, I: IntoIterator<Item = P>>(paths
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());
}
}
pub fn rebuild_if_embedded_path_changed<P: AsRef<Path>>(path: P) {
// Not necessary in debug builds, where rust-embed reads from the filesystem.
if cfg!(any(not(debug_assertions), windows)) {
rebuild_if_path_changed(path);
}
}
// Target OS for compiling our crates, as opposed to the build script.
pub fn target_os() -> String {
env_var("CARGO_CFG_TARGET_OS").unwrap()
}
pub fn target_os_is_apple() -> bool {
matches!(target_os().as_str(), "ios" | "macos")
}
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
/// `#[cfg(bsd)]`.
///
/// 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.
pub fn target_os_is_bsd() -> bool {
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!");
}
is_bsd
}
pub fn target_os_is_cygwin() -> bool {
target_os() == "cygwin"
}
#[macro_export]
macro_rules! as_os_strs {
[ $( $x:expr ),* $(,)? ] => {
{
use std::ffi::OsStr;
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
s.as_ref()
}
[
$( as_os_str($x) ),*
]
}
}
}

View File

@@ -1,10 +1,9 @@
[package]
name = "fish-build-man-pages"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[build-dependencies]
fish-build-helper.workspace = true

View File

@@ -1,24 +1,43 @@
use fish_build_helper::{env_var, workspace_root};
use std::path::Path;
use fish_build_helper::{as_os_strs, fish_doc_dir};
fn main() {
let sec1_dir = fish_doc_dir().join("man").join("man1");
// Running `cargo clippy` on a clean build directory panics, because when rust-embed
// tries to embed a directory which does not exist it will panic.
let man_dir = fish_build_helper::fish_build_dir().join("fish-man");
let sec1_dir = man_dir.join("man1");
// Running `cargo clippy` on a clean build directory panics, because when rust-embed tries to
// embed a directory which does not exist it will panic.
let _ = std::fs::create_dir_all(&sec1_dir);
if !cfg!(clippy) {
build_man(&sec1_dir);
let help_sections_path = Path::new(&env_var("OUT_DIR").unwrap()).join("help_sections.rs");
if env_var("FISH_USE_PREBUILT_DOCS").is_some_and(|v| v == "TRUE") {
std::fs::copy(
workspace_root().join("user_doc/src/help_sections.rs"),
help_sections_path,
)
.unwrap();
return;
}
std::fs::write(
help_sections_path.clone(),
r#"pub static HELP_SECTIONS: &str = "";"#,
)
.unwrap();
#[cfg(not(clippy))]
build_man(&man_dir, &sec1_dir, &help_sections_path);
}
fn build_man(sec1_dir: &Path) {
use fish_build_helper::{env_var, workspace_root};
use std::process::{Command, Stdio};
#[cfg(not(clippy))]
fn build_man(man_dir: &Path, sec1_dir: &Path, help_sections_path: &Path) {
use std::{
ffi::OsStr,
process::{Command, Stdio},
};
let workspace_root = workspace_root();
let doc_src_dir = workspace_root.join("doc_src");
let doctrees_dir = fish_doc_dir().join(".doctrees-man");
fish_build_helper::rebuild_if_paths_changed([
&workspace_root.join("CHANGELOG.rst"),
@@ -26,24 +45,39 @@ fn build_man(sec1_dir: &Path) {
&doc_src_dir,
]);
let args = as_os_strs![
"-j",
"auto",
"-q",
"-b",
"man",
"-c",
&doc_src_dir,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d",
&doctrees_dir,
&doc_src_dir,
&sec1_dir,
];
let help_sections_arg = format!("fish_help_sections_output={}", help_sections_path.display());
let args: &[&OsStr] = {
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
s.as_ref()
}
macro_rules! as_os_strs {
( [ $( $x:expr, )* ] ) => {
&[
$( as_os_str($x), )*
]
}
}
as_os_strs!([
"-j",
"auto",
"-q",
"-b",
"man",
"-c",
&doc_src_dir,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d",
&man_dir,
&doc_src_dir,
&sec1_dir,
"-D",
&help_sections_arg,
])
};
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env_var("FISH_BUILD_DOCS") == Some("0".to_owned()) {
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
@@ -60,12 +94,12 @@ fn build_man(sec1_dir: &Path) {
.spawn()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
assert_ne!(
env_var("FISH_BUILD_DOCS"),
Some("1".to_owned()),
"Could not find sphinx-build required to build man pages.\n\
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
);
if env_var("FISH_BUILD_DOCS") == Some("1".to_string()) {
panic!(
"Could not find sphinx-build required to build man pages.\n\
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
);
}
rsconf::warn!(
"Could not find sphinx-build required to build man pages. \
If you install Sphinx now, you need to trigger a rebuild to include man pages. \
@@ -92,10 +126,9 @@ fn build_man(sec1_dir: &Path) {
rsconf::warn!("sphinx-build: {}", String::from_utf8_lossy(&out.stderr));
}
assert_eq!(&String::from_utf8_lossy(&out.stdout), "");
assert!(
out.status.success(),
"sphinx-build failed to build the man pages."
);
if !out.status.success() {
panic!("sphinx-build failed to build the man pages.");
}
}
}
}

View File

@@ -1 +1 @@
include!(concat!(env!("OUT_DIR"), "/help_sections.rs"));

View File

@@ -1,14 +0,0 @@
[package]
name = "fish-color"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
fish-widestring.workspace = true
[lints]
workspace = true

View File

@@ -1,21 +0,0 @@
[package]
name = "fish-common"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
bitflags.workspace = true
fish-feature-flags.workspace = true
fish-widestring.workspace = true
libc.workspace = true
nix.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -1,5 +0,0 @@
use fish_build_helper::target_os_is_apple;
fn main() {
rsconf::declare_cfg("apple", target_os_is_apple());
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
[package]
name = "fish-fallback"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-common.workspace = true
fish-widecharwidth.workspace = true
fish-widestring.workspace = true
libc.workspace = true
widestring.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -1,5 +0,0 @@
use fish_build_helper::target_os_is_cygwin;
fn main() {
rsconf::declare_cfg("cygwin", target_os_is_cygwin());
}

View File

@@ -1,174 +0,0 @@
//! This file only contains fallback implementations of functions which have been found to be missing
//! or broken by the configuration scripts.
//!
//! Many of these functions are more or less broken and incomplete.
use fish_widecharwidth::{WcLookupTable, WcWidth};
use fish_widestring::prelude::*;
use std::cmp;
use std::sync::{
LazyLock,
atomic::{AtomicUsize, Ordering},
};
/// Width of ambiguous East Asian characters and, as of TR11, all private-use characters.
/// 1 is the typical default, but we accept any non-negative override via `$fish_ambiguous_width`.
pub static FISH_AMBIGUOUS_WIDTH: AtomicUsize = AtomicUsize::new(1);
/// Width of emoji characters.
///
/// This must be configurable because the value changed between Unicode 8 and Unicode 9, `wcwidth()`
/// is emoji-unaware, and terminal emulators do different things.
///
/// See issues like [#4539](https://github.com/fish-shell/fish-shell/issues/4539) and <https://github.com/neovim/neovim/issues/4976> for how painful this is.
///
/// Valid values are 1, and 2. 1 is the typical emoji width used in Unicode 8 while some newer
/// terminals use a width of 2 since Unicode 9.
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
pub static FISH_EMOJI_WIDTH: AtomicUsize = AtomicUsize::new(2);
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
pub fn fish_wcwidth(c: char) -> Option<usize> {
// Check for VS16 which selects emoji presentation. This "promotes" a character like U+2764
// (width 1) to an emoji (probably width 2). So treat it as width 1 so the sums work. See #2652.
// VS15 selects text presentation.
let variation_selector_16 = '\u{FE0F}';
let variation_selector_15 = '\u{FE0E}';
if c == variation_selector_16 {
return Some(1);
} else if c == variation_selector_15 {
return Some(0);
}
// Check for Emoji_Modifier property. Only the Fitzpatrick modifiers have this, in range
// 1F3FB..1F3FF. This is a hack because such an emoji appearing on its own would be drawn as
// width 2, but that's unlikely to be useful. See #8275.
if ('\u{1F3FB}'..='\u{1F3FF}').contains(&c) {
return Some(0);
}
let width = WC_LOOKUP_TABLE.classify(c);
Some(match width {
WcWidth::NonPrint => return None,
WcWidth::NonCharacter | WcWidth::Combining | WcWidth::Unassigned => 0,
WcWidth::Ambiguous | WcWidth::PrivateUse => {
// TR11: "All private-use characters are by default classified as Ambiguous".
FISH_AMBIGUOUS_WIDTH.load(Ordering::Relaxed)
}
WcWidth::One => 1,
WcWidth::Two => 2,
WcWidth::WidenedIn9 => FISH_EMOJI_WIDTH.load(Ordering::Relaxed),
})
}
pub fn fish_wcswidth(s: &wstr) -> Option<usize> {
fish_wcswidth_canonicalizing(s, std::convert::identity)
}
pub fn fish_wcswidth_canonicalizing(s: &wstr, canonicalize: fn(char) -> char) -> Option<usize> {
let chars = s.chars().map(canonicalize);
// ascii fast path
if chars.clone().all(|c| c.is_ascii() && !c.is_ascii_control()) {
return Some(s.len());
}
let mut result = 0;
for c in chars {
result += fish_wcwidth(c)?;
}
Some(result)
}
pub fn wcscasecmp(lhs: &wstr, rhs: &wstr) -> cmp::Ordering {
wcscasecmp_fuzzy(lhs, rhs, std::convert::identity)
}
/// Compare two wide strings in a case-insensitive fashion
pub fn wcscasecmp_fuzzy(
lhs: &wstr,
rhs: &wstr,
extra_canonicalization: fn(char) -> char,
) -> cmp::Ordering {
lowercase(lhs.chars())
.map(extra_canonicalization)
.cmp(lowercase(rhs.chars()).map(extra_canonicalization))
}
pub fn lowercase(chars: impl Iterator<Item = char>) -> impl Iterator<Item = char> {
lowercase_impl(chars, |c| c.to_lowercase())
}
pub fn lowercase_rev(chars: impl DoubleEndedIterator<Item = char>) -> impl Iterator<Item = char> {
lowercase_impl(chars.rev(), |c| c.to_lowercase().rev())
}
fn lowercase_impl<ToLowercase: Iterator<Item = char>>(
chars: impl Iterator<Item = char>,
to_lowercase: fn(char) -> ToLowercase,
) -> impl Iterator<Item = char> {
/// This struct streams the underlying lowercase chars of a string without allocating.
struct ToLowerBuffer<Chars: Iterator<Item = char>, ToLowercase: Iterator<Item = char>> {
to_lowercase: fn(char) -> ToLowercase,
current: ToLowercase,
chars: Chars,
}
impl<Chars: Iterator<Item = char>, ToLowercase: Iterator<Item = char>> Iterator
for ToLowerBuffer<Chars, ToLowercase>
{
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
if let Some(c) = self.current.next() {
return Some(c);
}
self.current = (self.to_lowercase)(self.chars.next()?);
self.current.next()
}
}
impl<Chars: Iterator<Item = char>, ToLowercase: Iterator<Item = char>>
ToLowerBuffer<Chars, ToLowercase>
{
pub fn new(mut chars: Chars, to_lowercase: fn(char) -> ToLowercase) -> Self {
Self {
to_lowercase,
current: chars.next().map_or_else(
|| {
let mut empty = to_lowercase('a');
let _ = empty.next();
debug_assert!(empty.next().is_none());
empty
},
to_lowercase,
),
chars,
}
}
}
ToLowerBuffer::new(chars, to_lowercase)
}
#[cfg(test)]
mod tests {
use super::wcscasecmp;
use fish_widestring::prelude::*;
use std::cmp::Ordering;
#[test]
fn test_wcscasecmp() {
// Comparison with empty
assert_eq!(wcscasecmp(L!("a"), L!("")), Ordering::Greater);
assert_eq!(wcscasecmp(L!(""), L!("a")), Ordering::Less);
assert_eq!(wcscasecmp(L!(""), L!("")), Ordering::Equal);
// Basic comparison
assert_eq!(wcscasecmp(L!("A"), L!("a")), Ordering::Equal);
assert_eq!(wcscasecmp(L!("B"), L!("a")), Ordering::Greater);
assert_eq!(wcscasecmp(L!("A"), L!("B")), Ordering::Less);
// Multi-byte comparison
assert_eq!(wcscasecmp(L!("İ"), L!("i\u{307}")), Ordering::Equal);
assert_eq!(wcscasecmp(L!("ia"), L!("İa")), Ordering::Less);
}
}

View File

@@ -1,13 +0,0 @@
[package]
name = "fish-feature-flags"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-widestring.workspace = true
[lints]
workspace = true

View File

@@ -1,24 +0,0 @@
[package]
name = "fish-fluent-extraction"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
description = "proc-macro for extracting IDs for fluent translation"
[lib]
proc-macro = true
[dependencies]
fish-tempfile.workspace = true
fluent-ftl-tools.workspace = true
fluent-syntax.workspace = true
proc-macro2.workspace = true
syn.workspace = true
[build-dependencies]
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -1,3 +0,0 @@
fn main() {
rsconf::rebuild_if_env_changed("FISH_FLUENT_EXTRACTION_DIR");
}

View File

@@ -1,105 +0,0 @@
extern crate proc_macro;
use fluent_ftl_tools::{
HasEntries as _, format_resource, parse_str_as_syntax_resource, serialize_resource,
};
use proc_macro::TokenStream;
use std::{
collections::HashSet,
ffi::{OsStr, OsString},
io::Write as _,
path::PathBuf,
};
use syn::{
Ident, LitStr, Token,
parse::{Parse, ParseStream},
parse_macro_input,
};
struct LocalizeDefinition {
message_id: String,
message_definition: String,
variables: Vec<String>,
}
impl Parse for LocalizeDefinition {
fn parse(input: ParseStream) -> syn::Result<Self> {
let message_id = input.parse::<LitStr>()?.value();
input.parse::<Token![=]>()?;
let message_definition = input.parse::<LitStr>()?.value();
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
fn parse_key(input: ParseStream) -> syn::Result<String> {
let key = input.parse::<Ident>()?;
Ok(format!("{key}"))
}
let variables = Vec::from_iter(input.parse_terminated(parse_key, Token![,])?);
Ok(Self {
message_id,
message_definition,
variables,
})
}
}
fn check(localize_definition: &LocalizeDefinition) -> String {
let message = format!(
"{} = {}",
localize_definition.message_id, localize_definition.message_definition
);
let resource = parse_str_as_syntax_resource(&message)
.unwrap_or_else(|err| panic!("Failed to parse Fluent message\n{message}\n\n{err}"));
let resource_entries = &resource.body;
assert_eq!(
resource_entries.len(),
1,
"Expected exactly one Fluent entry specified via macro."
);
assert!(
matches!(resource_entries[0], fluent_syntax::ast::Entry::Message(_)),
"Expected definition of Fluent message, but got {:?}",
resource_entries[0]
);
let formatted_resource = format_resource(resource.clone())
.unwrap_or_else(|err| panic!("Resource is not formatted correctly:\n{err}"));
let formatted_resource_string = serialize_resource(&formatted_resource);
let formatted_resource_string_trimmed = formatted_resource_string.trim();
assert!(
message == formatted_resource_string_trimmed,
"Message is not formatted correctly.\nActual:\n{message}\nExpected:\n{formatted_resource_string_trimmed}"
);
let mut expected_variables = HashSet::new();
for variable in &localize_definition.variables {
assert!(
expected_variables.insert(variable.as_str()),
"Variable {variable} is used as a key more than once."
);
}
resource.check_if_expected_variables_match_message(&expected_variables, &localize_definition.message_id).unwrap_or_else(|err| {
panic!("Variables used in macro key-value pairs do not match variables used in Fluent message:\n{err}");
});
message
}
fn extract(message: &str, dir_path: &OsStr) {
let dir = PathBuf::from(dir_path);
let (path, result) = fish_tempfile::create_file_with_retry(|| {
dir.join(fish_tempfile::random_filename(OsString::new()))
});
let mut file = result.unwrap_or_else(|e| {
panic!("Failed to create temporary file {path:?}:\n{e}");
});
file.write_all(message.as_bytes()).unwrap();
file.write_all(b"\n").unwrap();
}
#[proc_macro]
pub fn fluent_extract(input: TokenStream) -> TokenStream {
if let Some(dir_path) = std::env::var_os("FISH_FLUENT_EXTRACTION_DIR") {
let localize_definition = parse_macro_input!(input as LocalizeDefinition);
let message = check(&localize_definition);
extract(&message, &dir_path);
}
TokenStream::new()
}

View File

@@ -1,30 +0,0 @@
[package]
name = "fish-fluent"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
cfg-if.workspace = true
fish-build-helper.workspace = true
fish-localization.workspace = true
fish-fluent-extraction = { workspace = true, optional = true }
fluent.workspace = true
rust-embed.workspace = true
unic-langid.workspace = true
widestring.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
[dev-dependencies]
fluent-ftl-tools.workspace = true
[features]
fluent-extract = ["dep:fish-fluent-extraction"]
localize-messages = []
[lints]
workspace = true

View File

@@ -1,4 +0,0 @@
fn main() {
use fish_build_helper::{ftl_dir, rebuild_if_embedded_path_changed};
rebuild_if_embedded_path_changed(ftl_dir());
}

View File

@@ -1,360 +0,0 @@
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
fmt::Write as _,
sync::{LazyLock, Mutex},
};
use cfg_if::cfg_if;
use fish_localization::{
DEFAULT_LANGUAGE, Language, LocalizationLanguage, define_localization_language_type,
};
use fluent::{
FluentArgs, FluentResource, FluentValue, concurrent::FluentBundle, types::FluentNumber,
};
use rust_embed::RustEmbed;
#[cfg(feature = "fluent-extract")]
pub extern crate fish_fluent_extraction;
use unic_langid::LanguageIdentifier;
type Bundle = &'static FluentBundle<FluentResource>;
type NamedBundle = (Language<'static>, Bundle);
pub type LocalizedMessage = Cow<'static, str>;
static LANGUAGE_BUNDLES: LazyLock<Mutex<HashMap<Language, Bundle>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static DEFAULT_BUNDLE: LazyLock<NamedBundle> =
LazyLock::new(|| (DEFAULT_LANGUAGE, make_bundle(DEFAULT_LANGUAGE)));
static LANGUAGE_BUNDLE_PRECEDENCE: LazyLock<Mutex<Vec<NamedBundle>>> =
LazyLock::new(|| Mutex::new(vec![*DEFAULT_BUNDLE]));
cfg_if!(
if #[cfg(feature = "localize-messages")] {
#[derive(RustEmbed)]
#[folder = "../../localization/fluent/"]
#[include = "*.ftl"]
struct FtlFiles;
} else {
#[derive(RustEmbed)]
#[folder = "../../localization/fluent/"]
#[include = "en.ftl"]
struct FtlFiles;
}
);
define_localization_language_type! {FluentLocalizationLanguage}
static AVAILABLE_LANGUAGES: LazyLock<HashSet<FluentLocalizationLanguage>> = LazyLock::new(|| {
HashSet::from_iter(FtlFiles::iter().map(|language| {
let suffix = ".ftl";
let language = Language(match language {
Cow::Borrowed(language) => language.strip_suffix(suffix).unwrap(),
Cow::Owned(mut language) => {
assert!(language.ends_with(suffix));
language.truncate(language.len() - suffix.len());
Box::leak(Box::new(language))
}
});
FluentLocalizationLanguage(language)
}))
});
pub fn get_available_languages() -> &'static HashSet<FluentLocalizationLanguage> {
&AVAILABLE_LANGUAGES
}
fn read_ftl_file(path: &str) -> String {
let file = FtlFiles::get(path)
.unwrap_or_else(|| panic!("Tried to get FTL file {path} which does not exist."));
String::from_utf8(Vec::from(file.data))
.unwrap_or_else(|e| panic!("Content of {path} is not valid UTF-8: {e}"))
}
fn make_bundle(language: Language<'static>) -> Bundle {
let langid: LanguageIdentifier = language
.parse()
.map_err(|e| format!("Failed to parse language identifier {language}: {e}"))
.unwrap();
let mut bundle = FluentBundle::new_concurrent(vec![langid]);
let file_data = read_ftl_file(&format!("{language}.ftl"));
// Error handling could use fluent_ftl_tools::display_parse_errors(), but that would require
// making the crate a regular dependency.
match FluentResource::try_new(file_data) {
Ok(res) => {
bundle
.add_resource(res)
.map_err(|e| {
format!(
"Failed to add FTL resources to the bundle for language {language}: {e:?}"
)
})
.unwrap();
// Isolation marks can result in undesirable behavior if the terminal does not support
// them properly.
// Turn this off for now.
// If we add detection for terminal support, we could enable it conditionally.
// Without these marks, text order can be incorrect when right-to-left characters are
// involved.
// https://www.w3.org/International/questions/qa-bidi-unicode-controls
// https://github.com/fish-shell/fish-shell/pull/11928#discussion_r2488850606
bundle.set_use_isolating(false);
// Leak to create static reference.
Box::leak(Box::new(bundle))
}
Err((_resource, errors)) => {
let mut error_string = format!("Errors parsing FTL file for {language}:\n");
for error in errors {
let _ = writeln!(error_string, "{error}");
}
panic!("{error_string}");
}
}
}
/// Set the order in which languages should be tried for localization.
/// The default language (en) will be added to the end of the list.
/// If the provided list contains `en`, anything after it will be ignored, since we assume that every
/// message can be localized in English, so there is no point in specifying further fallback
/// options.
/// This function also takes care of lazily loading and parsing data from the embedded FTL files,
/// so no additional initialization is needed.
pub fn set_language_precedence(precedence: &[FluentLocalizationLanguage]) {
let new_precedence = {
let mut bundles = LANGUAGE_BUNDLES.lock().unwrap();
// Only take the languages preceding the default language, since it is assumed that everything
// is translatable in the default language.
let mut new_precedence: Vec<_> = precedence
.iter()
.take_while(|&lang| Language::from(lang) != DEFAULT_LANGUAGE)
.map(|lang| {
let language = lang.into();
// If a bundle already exists for the language, use it.
// Otherwise create a new one and cache it.
let bundle = bundles.get(&language).copied().unwrap_or_else(|| {
let bundle = make_bundle(language);
bundles.insert(language, bundle);
bundle
});
(language, bundle)
})
.collect();
// Add the default language at the end of the precedence list.
new_precedence.push(*DEFAULT_BUNDLE);
new_precedence
};
*LANGUAGE_BUNDLE_PRECEDENCE.lock().unwrap() = new_precedence;
}
pub fn get_language_precedence() -> Vec<Language<'static>> {
let language_precedence = LANGUAGE_BUNDLE_PRECEDENCE.lock().unwrap();
language_precedence.iter().map(|&(lang, _)| lang).collect()
}
/// Use the [`localize!`] macro instead of calling this directly.
/// Panics on errors.
pub fn format_localized(id: &str, args: &FluentArgs) -> LocalizedMessage {
let mut errors = vec![];
let bundle_precedence = LANGUAGE_BUNDLE_PRECEDENCE.lock().unwrap();
for (_, bundle) in bundle_precedence.iter() {
let Some(message) = bundle.get_message(id) else {
continue;
};
let pattern = message.value().expect("Message has no value.");
let value = bundle.format_pattern(pattern, Some(args), &mut errors);
// NOTE: Unused arguments are not considered errors.
if !errors.is_empty() {
let mut error_message = format!(
"Unexpected formatting errors occurred for message ID '{id}' in language '{}':\n",
bundle.locales[0].language
);
for error in errors {
let _ = writeln!(error_message, "{error}");
}
panic!("{error_message}");
}
return value;
}
panic!("Message '{id}' not available in any catalog.")
}
/// Call this to localize a message with Fluent.
/// The first argument is a string literal, which defines a Fluent message ID.
/// It is followed by a mandatory `=` and the English version of the message as a string literal.
/// The message definition must be valid Fluent syntax. Specifically it must be permissible as the
/// definition of a Fluent message.
/// In the simplest case, it will just be a regular string.
/// If Fluent variables should be used, they need to appear in the message definition, e.g.
/// `{ $example_variable }`. Then, the variable must also be specified as a key-value pair in the
/// arguments of the [`localize!`] macro, as demonstrated in the example below.
/// The key is the Fluent variable name, which also needs to be a syntactically valid Rust
/// identifier, and the value is whatever the variable should be replaced by when formatting the
/// localized message.
/// It is considered an error if the variables specified in the message definition do not match the
/// variables specified via keys in subsequent arguments to the macro.
/// The order of key-value pairs can be chosen and modified arbitrarily, and variables may appear
/// more than once in the message definition.
///
/// ```
/// # use fish_fluent::localize;
/// # // Note that `localize!` usage in doc-texts is not checked.
/// let example_message = localize!("test-with-args" = "Two arguments: { $first }, { $second }", first = "foo", second = 42);
/// assert_eq!(example_message, "Two arguments: foo, 42");
/// ```
///
/// Note that changing the message ID or the message definition has consequences for translations.
/// If such a change is made, our tooling will automatically detect it and require the developer
/// making the change to specify what should happen to translations.
/// See `cargo xtask fluent resolve-outdated`.
#[macro_export]
macro_rules! localize {
($id:literal = $message:literal $(, $key:ident = $value:expr)* $(,)?) => {
{
use $crate::ToFluentValue as _;
#[cfg(feature = "fluent-extract")]
fish_fluent_extraction::fluent_extract!($id = $message $(, $key)*);
let mut args = fluent::FluentArgs::new();
$(
args.set(stringify!($key), $value.to_fluent_value());
)*
$crate::format_localized($id, &args)
}
};
}
/// Define a function calling [`localize!`] and returning the result.
/// This macro can be used when multiple locations need access to the same message.
/// Do not use [`localize!`] with the same message ID more than once.
/// Instead, define a function which takes the Fluent variables of the message as arguments and
/// internally calls [`localize!`]. Then, use this function wherever the message is needed.
/// This macro helps with avoiding some boilerplate. Its first argument is the name of the function
/// which should be defined. Then, a key-value pair specifying the message ID and English definition
/// follows, in the same format as for [`localize!`]. The remaining arguments are the names of
/// Fluent variables which appear in the message definition. These will become the function's
/// arguments names and they will be used in the internal [`localize!`] call.
#[macro_export]
macro_rules! localize_fn {
($vis:vis $fn:ident, $id:literal = $message:literal $(, $key:ident )* $(,)?) => {
$vis fn $fn<'a>($($key: impl $crate::ToFluentValue<'a>),*) -> $crate::LocalizedMessage {
localize!(
$id = $message,
$($key = $key),*
)
}
};
}
/// Trait to account for types which don't have a `Into<FluentValue>` implementation, i.e.
/// widestrings.
/// Can be removed once we no longer use such types.
pub trait ToFluentValue<'a> {
fn to_fluent_value(self) -> FluentValue<'a>;
}
macro_rules! impl_to_fluent_value_wrapper {
($($t:ty),* $(,)?) => {
$(
impl<'a> ToFluentValue<'a> for $t {
fn to_fluent_value(self) -> FluentValue<'a> {
self.into()
}
}
)*
};
}
impl_to_fluent_value_wrapper! {
FluentNumber,
String,
f32, f64,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
}
macro_rules! impl_to_fluent_value_wrapper_ref {
($($t:ty),* $(,)?) => {
$(
impl<'a> ToFluentValue<'a> for &'a $t {
fn to_fluent_value(self) -> FluentValue<'a> {
self.into()
}
}
)*
};
}
impl_to_fluent_value_wrapper_ref! {
String, str,
f32, f64,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
}
impl<'a, T: Into<FluentValue<'a>>> ToFluentValue<'a> for Option<T> {
fn to_fluent_value(self) -> FluentValue<'a> {
self.into()
}
}
impl<'a> ToFluentValue<'a> for char {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for &char {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for widestring::Utf32String {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for &widestring::Utf32String {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
impl<'a> ToFluentValue<'a> for &widestring::Utf32Str {
fn to_fluent_value(self) -> FluentValue<'a> {
self.to_string().into()
}
}
#[cfg(test)]
mod tests {
use super::{DEFAULT_LANGUAGE, FtlFiles};
use crate::read_ftl_file;
use fluent_ftl_tools::consistency::check_all_resources;
#[test]
fn test_simple_message() {
let message_key_value = localize!(
"test-with-args" = "Two arguments: { $first }, { $second }",
first = "foo",
second = 42,
);
assert_eq!(message_key_value, "Two arguments: foo, 42");
}
#[test]
fn check_ftl_files() {
let default_resource_name = format!("{DEFAULT_LANGUAGE}.ftl");
let default_string = read_ftl_file(&default_resource_name);
let other_resources = FtlFiles::iter()
.map(|file_path| {
let file_string = read_ftl_file(&file_path);
(file_path.to_string(), file_string)
})
.collect::<Vec<_>>();
check_all_resources((&default_resource_name, default_string), other_resources)
.unwrap_or_else(|e| panic!("FTL resource checks failed:\n{e}"));
}
}

View File

@@ -1,21 +1,16 @@
[package]
name = "fish-gettext-extraction"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
description = "proc-macro for extracting strings for gettext translation"
version = "0.0.0"
repository.workspace = true
license.workspace = true
description = "proc-macro for extracting strings for gettext translation"
[lib]
proc-macro = true
[dependencies]
fish-tempfile.workspace = true
proc-macro2.workspace = true
[build-dependencies]
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -1,3 +0,0 @@
fn main() {
rsconf::rebuild_if_env_changed("FISH_GETTEXT_EXTRACTION_DIR");
}

View File

@@ -1,7 +1,6 @@
extern crate proc_macro;
use fish_tempfile::random_filename;
use proc_macro::TokenStream;
use std::{ffi::OsString, io::Write as _, path::PathBuf};
use std::{ffi::OsString, fs::OpenOptions, io::Write};
fn unescape_multiline_rust_string(s: String) -> String {
if !s.contains('\n') {
@@ -26,7 +25,7 @@ enum State {
Escaped => match c {
'\\' => {
unescaped.push('\\');
state = Ground;
state = Ground
}
'\n' => state = ContinuationLineLeadingWhitespace,
_ => panic!("Unsupported escape sequence '\\{c}' in message string '{s}'"),
@@ -35,7 +34,7 @@ enum State {
' ' | '\t' => (),
_ => {
unescaped.push(c);
state = Ground;
state = Ground
}
},
}
@@ -43,22 +42,18 @@ enum State {
unescaped
}
// Each entry is written to a fresh file to avoid race conditions arising when there are multiple
// unsynchronized writers to the same file.
fn write_po_entry_to_file(message: &TokenStream, dir: &OsString) {
fn append_po_entry_to_file(message: &TokenStream, file_name: &OsString) {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(file_name)
.unwrap_or_else(|e| panic!("Could not open file {file_name:?}: {e}"));
let message_string = unescape_multiline_rust_string(message.to_string());
assert!(
!message_string.contains('\n'),
"Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'"
);
let msgid_without_quotes = &message_string[1..(message_string.len() - 1)];
// We don't want leading or trailing whitespace in our messages.
let trimmed_msgid = msgid_without_quotes.trim();
assert_eq!(msgid_without_quotes, trimmed_msgid);
assert!(!trimmed_msgid.starts_with("\\n"));
assert!(!trimmed_msgid.ends_with("\\n"));
assert!(!trimmed_msgid.starts_with("\\t"));
assert!(!trimmed_msgid.ends_with("\\t"));
if message_string.contains('\n') {
panic!(
"Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'"
)
}
// Crude check for format strings. This might result in false positives.
let format_string_annotation = if message_string.contains('%') {
"#, c-format\n"
@@ -66,41 +61,35 @@ fn write_po_entry_to_file(message: &TokenStream, dir: &OsString) {
""
};
let po_entry = format!("{format_string_annotation}msgid {message_string}\nmsgstr \"\"\n\n");
let dir = PathBuf::from(dir);
let (path, result) =
fish_tempfile::create_file_with_retry(|| dir.join(random_filename(OsString::new())));
let mut file = result.unwrap_or_else(|e| {
panic!("Failed to create temporary file {path:?}:\n{e}");
});
file.write_all(po_entry.as_bytes()).unwrap();
}
/// The `message` is passed through unmodified.
/// If `FISH_GETTEXT_EXTRACTION_DIR` is defined in the environment,
/// the message ID is written into a new file in this directory,
/// If `FISH_GETTEXT_EXTRACTION_FILE` is defined in the environment,
/// this file is used to write the message,
/// so that it can then be used for generating gettext PO files.
/// The `message` must be a string literal.
///
/// # Panics
///
/// This macro panics if the `FISH_GETTEXT_EXTRACTION_DIR` variable is set and `message` has an
/// This macro panics if the `FISH_GETTEXT_EXTRACTION_FILE` variable is set and `message` has an
/// unexpected format.
/// Note that for example `concat!(...)` cannot be passed to this macro, because expansion works
/// outside in, meaning this macro would still see the `concat!` macro invocation, instead of a
/// string literal.
#[proc_macro]
pub fn gettext_extract(message: TokenStream) -> TokenStream {
if let Some(dir_path) = std::env::var_os("FISH_GETTEXT_EXTRACTION_DIR") {
if let Some(file_path) = std::env::var_os("FISH_GETTEXT_EXTRACTION_FILE") {
let pm2_message = proc_macro2::TokenStream::from(message.clone());
let mut token_trees = pm2_message.into_iter();
let first_token = token_trees
.next()
.expect("gettext_extract got empty token stream. Expected one token.");
assert!(
token_trees.next().is_none(),
"Invalid number of tokens passed to gettext_extract. Expected one token, but got more."
);
if token_trees.next().is_some() {
panic!(
"Invalid number of tokens passed to gettext_extract. Expected one token, but got more."
)
}
let proc_macro2::TokenTree::Group(group) = first_token else {
panic!("Expected group in gettext_extract, but got: {first_token:?}");
};
@@ -108,12 +97,13 @@ pub fn gettext_extract(message: TokenStream) -> TokenStream {
let first_group_token = group_tokens
.next()
.expect("gettext_extract expected one group token but got none.");
assert!(
group_tokens.next().is_none(),
"Invalid number of tokens in group passed to gettext_extract. Expected one token, but got more."
);
if group_tokens.next().is_some() {
panic!(
"Invalid number of tokens in group passed to gettext_extract. Expected one token, but got more."
)
}
if let proc_macro2::TokenTree::Literal(_) = first_group_token {
write_po_entry_to_file(&message, &dir_path);
append_po_entry_to_file(&message, &file_path);
} else {
panic!("Expected literal in gettext_extract, but got: {first_group_token:?}");
}

View File

@@ -1,19 +1,16 @@
[package]
name = "fish-gettext-maps"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
fish-localization.workspace = true
phf.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
fish-gettext-mo-file-parser.workspace = true
fish-localization.workspace = true
phf_codegen.workspace = true
rsconf.workspace = true

View File

@@ -5,23 +5,24 @@
};
use fish_build_helper::env_var;
use fish_localization::Language;
fn main() {
let cache_dir =
PathBuf::from(fish_build_helper::fish_build_dir()).join("fish-localization-map-cache");
embed_localizations(&cache_dir);
fish_build_helper::rebuild_if_path_changed(fish_build_helper::po_dir());
fish_build_helper::rebuild_if_path_changed(fish_build_helper::workspace_root().join("po"));
}
fn embed_localizations(cache_dir: &Path) {
use fish_gettext_mo_file_parser::parse_mo_file;
use std::{
fs::File,
io::{BufWriter, Write as _},
io::{BufWriter, Write},
};
let po_dir = fish_build_helper::workspace_root().join("po");
// Ensure that the directory is created, because clippy cannot compile the code if the
// directory does not exist.
std::fs::create_dir_all(cache_dir).unwrap();
@@ -40,7 +41,7 @@ fn embed_localizations(cache_dir: &Path) {
"Could not find msgfmt required to build message catalogs. \
Localization will not work. \
If you install gettext now, you need to trigger a rebuild to include localization support. \
For example by running `touch localization/po` followed by the build command."
For example by running `touch po` followed by the build command."
);
}
Err(e) => {
@@ -49,22 +50,22 @@ fn embed_localizations(cache_dir: &Path) {
Ok(output) => {
let has_check_format =
String::from_utf8_lossy(&output.stdout).contains("--check-format");
for dir_entry_result in fish_build_helper::po_dir().read_dir().unwrap() {
for dir_entry_result in po_dir.read_dir().unwrap() {
let dir_entry = dir_entry_result.unwrap();
let po_file_path = dir_entry.path();
if po_file_path.extension() != Some(OsStr::new("po")) {
continue;
}
let language = po_file_path
let lang = po_file_path
.file_stem()
.expect("All entries in the po directory must be regular files.");
let language = language.to_str().unwrap();
let language = lang.to_str().unwrap().to_owned();
// Each language gets its own static map for the mapping from message in the source code to
// the localized version.
let map_name = format!("LANG_MAP_{language}");
let cached_map_path = cache_dir.join(language);
let cached_map_path = cache_dir.join(lang);
// Include the file containing the map for this language in the main generated file.
writeln!(
@@ -75,10 +76,7 @@ fn embed_localizations(cache_dir: &Path) {
.unwrap();
// Map from the language identifier to the map containing the localizations for this
// language.
catalogs.entry(
Language(Box::leak(Box::new(language.to_owned()))),
format!("&{map_name}"),
);
catalogs.entry(language, format!("&{map_name}"));
if let Ok(metadata) = std::fs::metadata(&cached_map_path) {
// Cached map file exists, but might be outdated.
@@ -87,7 +85,7 @@ fn embed_localizations(cache_dir: &Path) {
if cached_map_mtime > po_mtime {
// Cached map file is considered up-to-date.
continue;
}
};
}
// Generate the map file.
@@ -100,7 +98,7 @@ fn embed_localizations(cache_dir: &Path) {
cmd = cmd.arg("--check-format");
} else {
tmp_mo_file = Some(cache_dir.join("messages.mo"));
}
};
cmd.arg(format!(
"--output-file={}",
tmp_mo_file
@@ -111,11 +109,12 @@ fn embed_localizations(cache_dir: &Path) {
.output()
.unwrap()
};
assert!(
output.status.success(),
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
if !output.status.success() {
panic!(
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
}
let mo_data =
tmp_mo_file.map_or(output.stdout, |path| std::fs::read(path).unwrap());
@@ -143,7 +142,7 @@ fn to_raw_str(s: &str) -> String {
write!(
&mut cached_map_file,
"static {}: phf::Map<&'static str, &'static str> = {}",
map_name,
&map_name,
single_language_localization_map.build()
)
.unwrap();
@@ -154,8 +153,7 @@ fn to_raw_str(s: &str) -> String {
write!(
&mut localization_map_file,
"use fish_localization::Language;\n\
pub static CATALOGS: phf::Map<Language, &phf::Map<&str, &str>> = {}",
"pub static CATALOGS: phf::Map<&str, &phf::Map<&str, &str>> = {}",
catalogs.build()
)
.unwrap();

View File

@@ -1,10 +1,9 @@
[package]
name = "fish-gettext-mo-file-parser"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[lints]
workspace = true

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;
const U32_SIZE: usize = size_of::<u32>();
const U32_SIZE: usize = std::mem::size_of::<u32>();
fn read_le_u32(bytes: &[u8]) -> u32 {
u32::from_le_bytes(bytes[..U32_SIZE].try_into().unwrap())
@@ -47,12 +47,9 @@ fn check_if_revision_is_supported(revision: u32) -> std::io::Result<()> {
}
fn as_usize(value: u32) -> usize {
const {
assert!(size_of::<u32>() <= size_of::<usize>());
}
// SAFETY: `usize` is guaranteed to be at least as wide as `u32` by the const assert above.
unsafe { usize::try_from(value).unwrap_unchecked() }
use std::mem::size_of;
const _: () = assert!(size_of::<u32>() <= size_of::<usize>());
usize::try_from(value).unwrap()
}
fn parse_strings(

View File

@@ -1,15 +0,0 @@
[package]
name = "fish-gettext"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-gettext-maps.workspace = true
fish-localization.workspace = true
phf.workspace = true
[lints]
workspace = true

View File

@@ -1,56 +0,0 @@
use fish_gettext_maps::CATALOGS;
use fish_localization::{Language, LocalizationLanguage, define_localization_language_type};
use std::{
collections::HashSet,
sync::{LazyLock, Mutex},
};
type Catalog = &'static phf::Map<&'static str, &'static str>;
static LANGUAGE_PRECEDENCE: Mutex<Vec<(Language, Catalog)>> = Mutex::new(Vec::new());
pub fn gettext(message_str: &'static str) -> Option<&'static str> {
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
// Use the localization from the highest-precedence language that has one available.
for (_, catalog) in language_precedence.iter() {
if let Some(localized_str) = catalog.get(message_str) {
return Some(localized_str);
}
}
None
}
define_localization_language_type! {GettextLocalizationLanguage}
static AVAILABLE_LANGUAGES: LazyLock<HashSet<GettextLocalizationLanguage>> = LazyLock::new(|| {
HashSet::from_iter(
CATALOGS
.entries()
.map(|(&language, _)| GettextLocalizationLanguage(language)),
)
});
pub fn get_available_languages() -> &'static HashSet<GettextLocalizationLanguage> {
&AVAILABLE_LANGUAGES
}
pub fn set_language_precedence(new_precedence: &[GettextLocalizationLanguage]) {
let catalogs = new_precedence
.iter()
.map(|lang| {
(
lang.into(),
*CATALOGS
.get(lang.as_ref())
.expect("Only languages for which catalogs exist may be passed to gettext."),
)
})
.collect();
*LANGUAGE_PRECEDENCE.lock().unwrap() = catalogs;
}
pub fn get_language_precedence() -> Vec<Language<'static>> {
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
language_precedence.iter().map(|&(lang, _)| lang).collect()
}

View File

@@ -1,14 +0,0 @@
[package]
name = "fish-localization"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-build-helper.workspace = true
phf_shared.workspace = true
[lints]
workspace = true

View File

@@ -1,86 +0,0 @@
use std::{borrow::Borrow, hash::Hash};
use phf_shared::{FmtConst, PhfBorrow, PhfHash};
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Language<'a>(pub &'a str);
pub const DEFAULT_LANGUAGE: Language = Language(fish_build_helper::DEFAULT_LANGUAGE);
impl<'a> std::ops::Deref for Language<'a> {
type Target = &'a str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> std::fmt::Display for Language<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for Language<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
impl Borrow<str> for Language<'_> {
fn borrow(&self) -> &str {
self.0
}
}
impl<'a> PhfHash for Language<'a> {
fn phf_hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.phf_hash(state);
}
}
impl<'a> FmtConst for Language<'a> {
fn fmt_const(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Language(")?;
self.0.fmt_const(f)?;
f.write_str(")")
}
}
impl<'a> PhfBorrow<Language<'a>> for Language<'a> {
fn borrow(&self) -> &Language<'a> {
self
}
}
pub trait LocalizationLanguage:
AsRef<Language<'static>> + Clone + Copy + Eq + Hash + Ord + PartialEq + PartialOrd + Borrow<str>
{
}
#[macro_export]
macro_rules! define_localization_language_type {
($name:ident) => {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(Language<'static>);
impl LocalizationLanguage for $name {}
impl AsRef<Language<'static>> for $name {
fn as_ref(&self) -> &Language<'static> {
&self.0
}
}
impl From<&$name> for Language<'static> {
fn from(value: &$name) -> Self {
value.0
}
}
impl std::borrow::Borrow<str> for $name {
fn borrow(&self) -> &str {
self.0.borrow()
}
}
};
}

View File

@@ -1,18 +1,17 @@
[package]
name = "fish-printf"
version = "0.2.1"
edition.workspace = true
rust-version.workspace = true
description = "printf implementation, based on musl"
version = "0.2.1"
repository.workspace = true
description = "printf implementation, based on musl"
license = "MIT"
[dependencies]
assert_matches.workspace = true
libc.workspace = true
widestring = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-width.workspace = true
widestring = { workspace = true, optional = true }
[lints]
workspace = true

View File

@@ -199,28 +199,27 @@ fn to_arg(self) -> Arg<'a> {
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[cfg(feature = "widestring")]
use widestring::utf32str;
#[test]
fn test_to_arg() {
assert_matches!("test".to_arg(), Arg::Str("test"));
assert_matches!(String::from("test").to_arg(), Arg::Str(_));
assert!(matches!("test".to_arg(), Arg::Str("test")));
assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
#[cfg(feature = "widestring")]
assert_matches!(utf32str!("test").to_arg(), Arg::WStr(_));
assert!(matches!(utf32str!("test").to_arg(), Arg::WStr(_)));
#[cfg(feature = "widestring")]
assert_matches!(WString::from("test").to_arg(), Arg::WStr(_));
assert_matches!(42f32.to_arg(), Arg::Float(_));
assert_matches!(42f64.to_arg(), Arg::Float(_));
assert_matches!('x'.to_arg(), Arg::UInt(120));
assert!(matches!(WString::from("test").to_arg(), Arg::WStr(_)));
assert!(matches!(42f32.to_arg(), Arg::Float(_)));
assert!(matches!(42f64.to_arg(), Arg::Float(_)));
assert!(matches!('x'.to_arg(), Arg::UInt(120)));
let mut usize_val: usize = 0;
assert_matches!((&mut usize_val).to_arg(), Arg::USizeRef(_));
assert_matches!(42i8.to_arg(), Arg::SInt(42));
assert_matches!(42i16.to_arg(), Arg::SInt(42));
assert_matches!(42i32.to_arg(), Arg::SInt(42));
assert_matches!(42i64.to_arg(), Arg::SInt(42));
assert_matches!(42isize.to_arg(), Arg::SInt(42));
assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
assert!(matches!(42i8.to_arg(), Arg::SInt(42)));
assert!(matches!(42i16.to_arg(), Arg::SInt(42)));
assert!(matches!(42i32.to_arg(), Arg::SInt(42)));
assert!(matches!(42i64.to_arg(), Arg::SInt(42)));
assert!(matches!(42isize.to_arg(), Arg::SInt(42)));
assert_eq!((-42i8).to_arg(), Arg::SInt(-42));
assert_eq!((-42i16).to_arg(), Arg::SInt(-42));
@@ -228,14 +227,14 @@ fn test_to_arg() {
assert_eq!((-42i64).to_arg(), Arg::SInt(-42));
assert_eq!((-42isize).to_arg(), Arg::SInt(-42));
assert_matches!(42u8.to_arg(), Arg::UInt(42));
assert_matches!(42u16.to_arg(), Arg::UInt(42));
assert_matches!(42u32.to_arg(), Arg::UInt(42));
assert_matches!(42u64.to_arg(), Arg::UInt(42));
assert_matches!(42usize.to_arg(), Arg::UInt(42));
assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
assert!(matches!(42u16.to_arg(), Arg::UInt(42)));
assert!(matches!(42u32.to_arg(), Arg::UInt(42)));
assert!(matches!(42u64.to_arg(), Arg::UInt(42)));
assert!(matches!(42usize.to_arg(), Arg::UInt(42)));
let ptr = std::ptr::from_ref(&42f32);
assert_matches!(ptr.to_arg(), Arg::UInt(_));
let ptr = &42f32 as *const f32;
assert!(matches!(ptr.to_arg(), Arg::UInt(_)));
}
#[test]

View File

@@ -266,7 +266,7 @@ fn should_round_up(&self, digit_idx: i32, remainder: u32, mod_base: u32) -> bool
if rounding_digit & 1 != 0 {
round += 2.0;
// round now has an odd lsb (though round itself is even).
debug_assert_ne!(round.to_bits() & 1, 0);
debug_assert!(round.to_bits() & 1 != 0);
}
// Set 'small' to a value which is less than halfway, exactly halfway, or more than halfway

View File

@@ -4,7 +4,6 @@
use super::locale::Locale;
use super::printf_impl::{ConversionSpec, Error, ModifierFlags, pad};
use assert_matches::debug_assert_matches;
use decimal::{DIGIT_WIDTH, Decimal, DigitLimit};
use std::cmp::min;
use std::fmt::Write;
@@ -130,10 +129,10 @@ pub(crate) fn format_float(
) -> Result<usize, Error> {
// Only float conversions are expected.
type CS = ConversionSpec;
debug_assert_matches!(
debug_assert!(matches!(
conv_spec,
CS::e | CS::E | CS::f | CS::F | CS::g | CS::G | CS::a | CS::A
);
));
let prefix = match (y.is_sign_negative(), flags.mark_pos, flags.pad_pos) {
(true, _, _) => "-",
(false, true, _) => "+",

View File

@@ -51,7 +51,7 @@ macro_rules! sprintf {
{
// May be no args!
#[allow(unused_imports)]
use $crate::ToArg as _;
use $crate::ToArg;
$crate::printf_c_locale(
$target,
$fmt,
@@ -84,7 +84,7 @@ macro_rules! sprintf {
///
/// let result = printf_c_locale(&mut output, fmt, &mut args);
///
/// assert_eq!(result, Ok(10));
/// assert!(result == Ok(10));
/// assert_eq!(output, "1.2346e+05");
/// ```
pub fn printf_c_locale(

View File

@@ -2,12 +2,11 @@
use super::arg::Arg;
use super::fmt_fp::format_float;
use super::locale::Locale;
use assert_matches::assert_matches;
use std::fmt::{self, Write};
use std::mem;
use std::result::Result;
use unicode_segmentation::UnicodeSegmentation as _;
use unicode_width::UnicodeWidthStr as _;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
#[cfg(feature = "widestring")]
use widestring::Utf32Str as wstr;
@@ -58,7 +57,7 @@ fn try_set(&mut self, c: char) -> bool {
'+' => self.mark_pos = true,
'\'' => self.grouped = true,
_ => return false,
}
};
true
}
}
@@ -302,7 +301,7 @@ pub(super) fn pad(
min_width: usize,
current_width: usize,
) -> fmt::Result {
assert_matches!(c, '0' | ' ');
assert!(c == '0' || c == ' ');
if current_width >= min_width {
return Ok(());
}
@@ -342,7 +341,7 @@ pub(super) fn pad(
///
/// let result = sprintf_locale(&mut output, fmt, &locale::EN_US_LOCALE, &mut args);
///
/// assert_eq!(result, Ok(12));
/// assert!(result == Ok(12));
/// assert_eq!(output, "1,234,567.89");
/// ```
pub fn sprintf_locale(
@@ -372,7 +371,7 @@ pub fn sprintf_locale(
}
// Consume the % at the start of the format specifier.
debug_assert_eq!(s.at(0), Some('%'));
debug_assert!(s.at(0) == Some('%'));
s.advance_by(1);
// Read modifier flags. '-' and '0' flags are mutually exclusive.
@@ -562,7 +561,7 @@ pub fn sprintf_locale(
};
// Numeric output should be empty iff the value is 0.
if spec_is_numeric && body.is_empty() {
debug_assert_eq!(arg.as_uint().unwrap(), 0);
debug_assert!(arg.as_uint().unwrap() == 0);
}
// Decide if we want to apply thousands grouping to the body, and compute its size.

View File

@@ -1,6 +1,7 @@
use crate::arg::ToArg;
use crate::locale::{C_LOCALE, EN_US_LOCALE, Locale};
use crate::{Error, FormatString as _, sprintf_locale};
use crate::{Error, FormatString, sprintf_locale};
use libc::c_char;
use std::f64::consts::{E, PI, TAU};
use std::ffi::CStr;
use std::fmt;
@@ -13,7 +14,7 @@ macro_rules! sprintf_check {
$(,)? // optional trailing comma
) => {
{
use unicode_width::UnicodeWidthStr as _;
use unicode_width::UnicodeWidthStr;
let mut target = String::new();
let mut args = [$($arg.to_arg()),*];
let len = $crate::printf_c_locale(
@@ -400,10 +401,8 @@ fn test_char() {
#[test]
fn test_ptr() {
assert_fmt!("%p", core::ptr::null::<()>() => "0");
let tmp = core::ptr::without_provenance::<()>(0xDEADBEEF);
assert_fmt!("%p", tmp => "0xdeadbeef");
assert_fmt!("%p", core::ptr::null::<u8>() => "0");
assert_fmt!("%p", 0xDEADBEEF_usize as *const u8 => "0xdeadbeef");
}
#[test]
@@ -863,8 +862,8 @@ fn test_float_hex_prec() {
let mut libc_sprintf = libc_sprintf_one_float_with_precision(&mut c_storage, c"%.*a");
let mut failed = false;
for sign in [1.0, -1.0] {
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E] {
for sign in [1.0, -1.0].into_iter() {
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E].into_iter() {
v *= sign;
for preci in 1..=200_usize {
rust_str.clear();
@@ -891,16 +890,10 @@ fn libc_sprintf_one_float_with_precision<'a>(
fmt: &'a CStr,
) -> impl FnMut(usize, f64) -> &'a str {
|preci, float_val| unsafe {
let storage_ptr = storage.as_mut_ptr();
let len = libc::snprintf(
storage_ptr.cast(),
storage.len(),
fmt.as_ptr(),
preci,
float_val,
);
let storage_ptr = storage.as_mut_ptr() as *mut c_char;
let len = libc::snprintf(storage_ptr, storage.len(), fmt.as_ptr(), preci, float_val);
assert!(len >= 0);
let sl = std::slice::from_raw_parts(storage_ptr, len as usize);
let sl = std::slice::from_raw_parts(storage_ptr as *const u8, len as usize);
std::str::from_utf8(sl).unwrap()
}
}

View File

@@ -1,14 +1,12 @@
[package]
name = "fish-tempfile"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
license.workspace = true
[dependencies]
nix = { workspace = true, features = ["feature", "fs"] }
rand.workspace = true
nix = { workspace = true, features = ["fs", "feature"] }
[lints]
workspace = true

View File

@@ -1,13 +1,5 @@
use std::{
ffi::OsString,
fs::{File, OpenOptions},
io::ErrorKind,
path::PathBuf,
};
use std::{fs::File, path::PathBuf};
use rand::distr::{Alphanumeric, Distribution as _};
#[must_use]
pub struct TempFile {
file: File,
path: PathBuf,
@@ -33,7 +25,6 @@ fn drop(&mut self) {
}
}
#[must_use]
pub struct TempDir {
path: PathBuf,
}
@@ -50,56 +41,20 @@ fn drop(&mut self) {
}
}
/// Creates a random filename with the given prefix.
/// Appends a random sequence of alphanumeric ASCII characters.
pub fn random_filename(mut prefix: OsString) -> OsString {
let mut rng = rand::rng();
let suffix_length = 10;
let random_part: String = Alphanumeric
.sample_iter(&mut rng)
.take(suffix_length)
.map(char::from)
.collect();
assert_eq!(random_part.len(), suffix_length);
prefix.push(random_part);
prefix
fn get_tmpdir() -> PathBuf {
PathBuf::from(std::env::var_os("TMPDIR").unwrap_or("/tmp".into()))
}
/// Tries to create a new file at the path returned by `generate_path`.
/// If a file already exists at this path, `generate_path` will be called again and file creation
/// will be retried. This is repeated until a new file is created, or an error occurs.
pub fn create_file_with_retry(
generate_path: impl Fn() -> PathBuf,
) -> (PathBuf, std::io::Result<File>) {
loop {
let path = generate_path();
match OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(&path)
{
Ok(file) => {
return (path, Ok(file));
}
Err(e) => {
if e.kind() != ErrorKind::AlreadyExists {
return (path, Err(e));
}
}
}
}
fn get_template() -> PathBuf {
get_tmpdir().join("fish_tmp_XXXXXX")
}
/// Tries to create a new temporary file in the operating system's default location for temporary
/// files.
/// Tries to create a new temporary file using `mkstemp`.
/// On success, a [`TempFile`] is returned.
/// When this struct is dropped, the backing file will be deleted.
pub fn new_file() -> std::io::Result<TempFile> {
let (path, result) = create_file_with_retry(|| {
std::env::temp_dir().join(random_filename(OsString::from("fish_tmp_")))
});
let file = result?;
let (fd, path) = nix::unistd::mkstemp(&get_template())?;
let file = File::from(fd);
Ok(TempFile { file, path })
}
@@ -107,8 +62,7 @@ pub fn new_file() -> std::io::Result<TempFile> {
/// On success, a [`TempDir`] is returned.
/// When this struct is dropped, the backing directory, including all its contents, will be deleted.
pub fn new_dir() -> std::io::Result<TempDir> {
let template = std::env::temp_dir().join("fish_tmp_XXXXXX");
let path = nix::unistd::mkdtemp(&template)?;
let path = nix::unistd::mkdtemp(&get_template())?;
Ok(TempDir { path })
}
@@ -116,12 +70,12 @@ pub fn new_dir() -> std::io::Result<TempDir> {
mod tests {
use std::{
fs::File,
io::{Read as _, Seek as _, Write as _},
io::{Read, Seek, Write},
};
#[test]
fn create_tempfile() {
let _ = super::new_file().unwrap();
super::new_file().unwrap();
}
#[test]
@@ -147,7 +101,7 @@ fn use_tempfile() {
#[test]
fn create_tempdir() {
let _ = super::new_dir().unwrap();
super::new_dir().unwrap();
}
#[test]

View File

@@ -1,17 +0,0 @@
[package]
name = "fish-util"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
errno.workspace = true
fish-widestring.workspace = true
libc.workspace = true
nix.workspace = true
rand.workspace = true
[lints]
workspace = true

View File

@@ -1,20 +0,0 @@
[package]
name = "fish-wcstringutil"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
fish-fallback.workspace = true
fish-widestring.workspace = true
# Only needed for cygwin detection.
# TODO(MSRV>=1.86): remove
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -1,3 +0,0 @@
fn main() {
rsconf::declare_cfg("cygwin", fish_build_helper::target_os_is_cygwin());
}

View File

@@ -1,15 +0,0 @@
[package]
name = "fish-wgetopt"
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
assert_matches.workspace = true
fish-wcstringutil.workspace = true
fish-widestring.workspace = true
[lints]
workspace = true

Some files were not shown because too many files have changed in this diff Show More