Compare commits

..

8 Commits

Author SHA1 Message Date
Johannes Altmanninger
00e987df15 completions/git: stop "git for-each-ref refs/remotes/" after 200ms
See #11535
2025-06-01 17:11:54 +02:00
Johannes Altmanninger
a6ee9be72a completions/git: only look at local branches and tags when computing 50 recent commits
Commit 836b6bea73 (git completions: Add commit hashes for `show`, 2015-12-18)
made

	git show foo<TAB>

magically expand "foo" to the commit hash of a recent commit.  I would
discourage from using raw commit IDs on the command line but I guess it's the
most convenient way sometimes. Probably this is still useful to some users even
though "git show :/foo" exists.  The set of target commits was "git log -50".

Commit 3c3bf7ffd7 (completions/git: Show recent commits *on all branches*
for rebase, 2021-03-12) and others expanded the set of target commits to "git
log -50 --all" for some commands. I think Git partially sorts the result
set before returning the top 50 entries, possibly causing a slowdown.
(A slowdown is easily reproducible with "time git log --max-count=50
>/dev/null" with and without "--all").

The "--all" flag seems excessive because
1. it looks at remote branches. There can be a lot of those, especially in
   a monorepo context or when Git is configured to fetch branches from
   multiple remotes.  See also #9248 where we explicitly decided not to sort
   remote branches.
2. it looks at refs that are neither branches nor tags.
   Some systems like Jujutsu or git-branchless use such refs to prevent their
   commits from being garbage-collected. Such refs typically have opaque
   (autogenerated) IDs and should normally not be shown in a generic Git UI.

I don't know if completion of commit IDs for these kinds of refs is worth it.
(Normally you'd run something like "git checkout remote-branch" instead.)

Exclude these two types of refs.  Do include HEAD which is not a branch or
tag.  In future we could include refs like REBASE_HEAD again; though again,
I don't know if those are needed for commit-ID completion.

Given

	$ git for-each-ref refs/tags | wc -l
	1033
	$ git for-each-ref refs/heads | wc -l   # local branches
	6
	$ git for-each-ref refs/remotes | wc -l # remote branches
	5270
	$ git for-each-ref refs/jj/keep | wc -l # Jujutsu
	8390

this results in a good speedup for completions:

	$ fish -c "time complete -C'git ' >/dev/null && time complete -C'git checkout hello' >/dev/null" &| grep .
	________________________________________________________
	Executed in   68.61 millis    fish           external
	   usr time   63.50 millis   55.17 millis    8.33 millis
	   sys time   13.41 millis   13.41 millis    0.00 millis
	________________________________________________________
	Executed in  911.49 millis    fish           external
	   usr time  359.17 millis   91.00 millis  268.18 millis
	   sys time  604.22 millis   24.70 millis  579.53 millis
	$ fish -c "time complete -C'git ' >/dev/null && time complete -C'git checkout hello' >/dev/null" &| grep .
	________________________________________________________
	Executed in   70.20 millis    fish           external
	   usr time   59.81 millis   59.81 millis    0.00 millis
	   sys time   19.39 millis   12.22 millis    7.17 millis
	________________________________________________________
	Executed in  449.83 millis    fish           external
	   usr time  240.37 millis   94.17 millis  146.21 millis
	   sys time  403.66 millis   29.66 millis  374.00 millis

Part of #11535
2025-06-01 17:11:54 +02:00
Johannes Altmanninger
ad28e17b50 completions/git: sort stash completions after branches and others
Completions like "stash@{1}" don't give a lot of information, unlike local
branches which are sorted by recency so let's put those first.
2025-06-01 17:11:54 +02:00
Johannes Altmanninger
a7d9f2293e completions/git: extract logic 2025-06-01 17:11:54 +02:00
Johannes Altmanninger
df92c93332 completions/git: extract function for adding completions for arbitrary revisions
Most commands take arbitrary revisions.  AFAICT, the order should always be
the same: we list local branches before remote branches before commit IDs etc.

Let's extract a function. This adds a various revision-types that were missing.
2025-06-01 17:11:54 +02:00
Johannes Altmanninger
aebae23533 completions/git: rebase --onto requires a revision argument 2025-06-01 17:11:54 +02:00
Johannes Altmanninger
69b5ad110a completions/git: add more special refs
These are things like .git/HEAD, i.e. the ones that are typically not
namespaced under .git/refs.  The list is taken from gitrevisions(7).
2025-06-01 17:11:54 +02:00
Johannes Altmanninger
17446ad20f completions/git: fix copy-paste error
This variable is never defined. It was copied from Git's
contrib/completion/git-completion.bash where $match is probably equivalent
to $(commandline -t).  I could measure a significant speedup passing down
this filter, so let's remove it for now.
2025-06-01 17:11:54 +02:00
610 changed files with 69899 additions and 58777 deletions

View File

@@ -1,6 +1,7 @@
image: alpine/edge
packages:
- cargo
- clang17-libclang
- cmake
- ninja
- pcre2-dev
@@ -23,4 +24,4 @@ tasks:
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests
ninja test

View File

@@ -20,4 +20,4 @@ tasks:
ninja
- test: |
cd fish/build
ninja fish_run_tests
ninja test

View File

@@ -5,6 +5,7 @@ packages:
- gettext
- gmake
- llvm
- terminfo-db
- ninja
- pcre2
- py311-pexpect
@@ -26,4 +27,4 @@ tasks:
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests
ninja test

View File

@@ -1,29 +0,0 @@
image: openbsd/latest
packages:
- cmake
- gcc
- gettext
- gmake
- llvm
- ninja
- pcre2
- py311-pexpect
- python
- rust
- tmux
sources:
- https://github.com/fish-shell/fish-shell
tasks:
- build: |
cd fish-shell
mkdir build
cd build
cmake -GNinja .. \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_DATADIR=share \
-DCMAKE_INSTALL_DOCDIR=share/doc/fish \
-DCMAKE_INSTALL_SYSCONFDIR=/etc
ninja
- test: |
cd fish-shell/build
ninja fish_run_tests

View File

@@ -1,5 +1,3 @@
skip: $CIRRUS_REPO_OWNER == 'fish-shell' && $CIRRUS_BRANCH == 'master'
env:
CIRRUS_CLONE_DEPTH: 100
CI: 1
@@ -8,19 +6,27 @@ linux_task:
matrix:
- name: alpine
container: &step
image: ghcr.io/fish-shell/fish-ci/alpine:latest
image: ghcr.io/krobelus/fish-ci/alpine:latest
memory: 4GB
- name: jammy
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/jammy:latest
image: ghcr.io/krobelus/fish-ci/jammy:latest
# - name: jammy-asan
# container:
# <<: *step
# image: ghcr.io/krobelus/fish-ci/jammy-asan:latest
# - name: focal-32bit
# container:
# <<: *step
# image: ghcr.io/krobelus/fish-ci/focal-32bit: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 ..
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCTEST_PARALLEL_LEVEL=6 ..
- ninja -j 6 fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
@@ -30,24 +36,35 @@ linux_arm_task:
- name: focal-arm64
arm_container:
image: ghcr.io/fish-shell/fish-ci/focal-arm64
- name: jammy-armv7-32bit
arm_container:
image: ghcr.io/fish-shell/fish-ci/jammy-armv7-32bit
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 ..
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCTEST_PARALLEL_LEVEL=6 ..
- ninja -j 6 fish
- file ./fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
# CI task disabled during RIIR transition
only_if: false && $CIRRUS_REPO_OWNER == 'fish-shell'
freebsd_task:
matrix:
- name: FreeBSD 14
# - name: FreeBSD 14
# freebsd_instance:
# image_family: freebsd-14-0-snap
- name: FreeBSD 13
freebsd_instance:
image: freebsd-14-3-release-amd64-ufs
image: freebsd-13-2-release-amd64
# - name: FreeBSD 12.3
# freebsd_instance:
# image: freebsd-12-3-release-amd64
tests_script:
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
- pkg install -y cmake-core devel/pcre2 devel/ninja misc/py-pexpect git-lite terminfo-db
# 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
@@ -60,7 +77,15 @@ freebsd_task:
- 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
# FreeBSD's pkg currently has rust 1.66.0 while we need rust 1.70.0+. Use rustup to install
# the latest, but note that it only installs rust per-user.
- sudo -u fish-user -s fetch -qo - https://sh.rustup.rs > rustup.sh
- sudo -u fish-user -s sh ./rustup.sh -y --profile=minimal
# `sudo -s ...` does not invoke a login shell so we need a workaround to make sure the
# rustup environment is configured for subsequent `sudo -s ...` commands.
# For some reason, this doesn't do the job:
# - sudo -u fish-user sh -c 'echo source \$HOME/.cargo/env >> $HOME/.cshrc'
- sudo -u fish-user -s cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCTEST_PARALLEL_LEVEL=1 ..
- sudo -u fish-user sh -c '. $HOME/.cargo/env; ninja -j 6 fish'
- sudo -u fish-user sh -c '. $HOME/.cargo/env; ninja fish_run_tests'
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'

View File

@@ -13,14 +13,10 @@ max_line_length = 100
indent_style = tab
[*.{md,rst}]
max_line_length = unset
trim_trailing_whitespace = false
[*.sh]
indent_size = 4
[build_tools/release.sh]
max_line_length = 72
[*.{sh,ac}]
indent_size = 2
[Dockerfile]
indent_size = 2
@@ -28,5 +24,5 @@ indent_size = 2
[share/{completions,functions}/**.fish]
max_line_length = unset
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
max_line_length = 72
[{COMMIT_EDITMSG,git-revise-todo}]
max_line_length = 80

4
.gitattributes vendored
View File

@@ -1,5 +1,7 @@
# normalize newlines
* text=auto eol=lf
* text=auto
*.fish text
*.bat eol=crlf
# let git show off diff hunk headers, help git diff -L:
# https://git-scm.com/docs/gitattributes

View File

@@ -1,43 +0,0 @@
name: Install dependencies for system tests
inputs:
include_sphinx:
description: Whether to install Sphinx
required: true
default: false
include_pcre:
description: Whether to install the PCRE library
required: false
default: true
permissions:
contents: read
runs:
using: "composite"
steps:
- shell: bash
env:
include_sphinx: ${{ inputs.include_sphinx }}
include_pcre: ${{ inputs.include_pcre }}
run: |
set -x
: "optional dependencies"
sudo apt install \
gettext \
$(if $include_pcre; then echo libpcre2-dev; fi) \
$(if $include_sphinx; then echo python3-sphinx; fi) \
;
: "system test dependencies"
sudo apt install \
diffutils $(: "for diff") \
git \
gettext \
less \
$(if ${{ inputs.include_pcre }}; then echo libpcre2-dev; fi) \
python3-pexpect \
tmux \
wget \
;
- uses: ./.github/actions/install-sphinx-markdown-builder
if: ${{ inputs.include_sphinx == 'true' }}

View File

@@ -1,14 +0,0 @@
name: Install sphinx-markdown-builder
permissions:
contents: read
runs:
using: "composite"
steps:
- shell: bash
run: |
set -x
commit=b259de1dc97573a71470a1d71c3d83535934136b
pip install git+https://github.com/krobelus/sphinx-markdown-builder@"$commit"
python -c 'import sphinx_markdown_builder'

View File

@@ -1,41 +0,0 @@
name: Rust Toolchain
inputs:
toolchain_channel:
description: Either "stable" or "msrv"
required: true
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- name: Set toolchain
env:
toolchain_channel: ${{ inputs.toolchain_channel }}
shell: bash
run: |
set -x
toolchain=$(
case "$toolchain_channel" in
(stable) echo 1.90 ;;
(msrv) echo 1.70 ;;
(*)
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
exit 1
;;
esac
)
printf 'TOOLCHAIN=%s\n' "$toolchain" >>"$GITHUB_ENV"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.TOOLCHAIN }}
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -1,21 +0,0 @@
name: Oldest Supported Rust Toolchain
inputs:
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: "msrv"
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -1,21 +0,0 @@
name: Stable Rust Toolchain
inputs:
targets:
description: Comma-separated list of target triples to install for this toolchain
required: false
components:
description: Comma-separated list of components to be additionally installed
required: false
permissions:
contents: read
runs:
using: "composite"
steps:
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: "stable"
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

View File

@@ -8,12 +8,16 @@ jobs:
label-and-milestone:
runs-on: ubuntu-latest
steps:
# - name: Checkout repository
# uses: actions/checkout@v2
- name: Set label and milestone
id: set-label-milestone
uses: actions/github-script@v7
with:
script: |
const completionsLabel = 'completions';
const completionsMilestone = 'fish next-3.x';
// Get changed files in the pull request
const prNumber = context.payload.pull_request.number;
@@ -37,4 +41,26 @@ jobs:
labels: [completionsLabel],
});
console.log(`PR ${prNumber} assigned label "${completionsLabel}"`);
// Get the list of milestones
const { data: milestones } = await github.rest.issues.listMilestones({
owner: context.repo.owner,
repo: context.repo.repo,
});
// Find the milestone id
const milestone = milestones.find(milestone => milestone.title === completionsMilestone);
if (milestone) {
// Set the milestone for the PR
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
milestone: milestone.number
});
console.log(`PR ${prNumber} assigned milestone "${completionsMilestone}"`);
} else {
console.error(`Milestone "${completionsMilestone}" not found`);
}
}

View File

@@ -1,126 +0,0 @@
name: Build Docker test images
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: docker-builds
env:
REGISTRY: ghcr.io
NAMESPACE: fish-ci
ONLY_FOR_REPO_OWNER: fish-shell
jobs:
check-docker-changes:
if: github.repository_owner == env.ONLY_FOR_REPO_OWNER
runs-on: ubuntu-latest
outputs:
docker-changed: ${{ steps.changes.outputs.docker }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
docker:
- 'docker/**'
docker-build:
needs: check-docker-changes
if: github.repository_owner == env.ONLY_FOR_REPO_OWNER && needs.check-docker-changes.outputs.docker-changed == 'true'
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: centos9
- os: ubuntu-latest
target: fedora
- os: ubuntu-latest
target: focal-32bit
- os: ubuntu-24.04-arm
target: focal-arm64
- os: ubuntu-latest
target: focal
- os: ubuntu-24.04-arm
target: jammy-armv7-32bit
- os: ubuntu-latest
target: jammy-asan
- os: ubuntu-latest
target: jammy-tsan
- os: ubuntu-latest
target: jammy
- os: ubuntu-latest
target: noble
- os: ubuntu-latest
target: opensuse-tumbleweed
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 }}
trigger-cirrus:
needs: [check-docker-changes, docker-build]
if: always() && github.repository_owner == env.ONLY_FOR_REPO_OWNER
runs-on: ubuntu-latest
steps:
- name: Trigger Cirrus CI
env:
CIRRUS_TOKEN: ${{ secrets.CIRRUS_TOKEN }}
run: |
set -x
# N.B. push-triggered workflows are usually from master.
branch=${{ github.ref_name }}
repository_id=${{ github.repository_id }}
curl -X POST \
-H "Authorization: Bearer $CIRRUS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation {
createBuild(input: {
repositoryId: \"$repository_id\",
branch: \"$branch\"
})
{ build { id } }
}"
}' \
https://api.cirrus-ci.com/graphql

View File

@@ -1,59 +0,0 @@
name: Lint
on: [push, pull_request]
permissions:
contents: read
jobs:
format:
runs-on: ubuntu-latest
steps:
- 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
- name: check format
run: PATH="target/debug:$PATH" build_tools/style.fish --all --check
clippy:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- rust_version: "stable"
features: ""
- rust_version: "stable"
features: "--no-default-features"
- rust_version: "msrv"
features: ""
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain
with:
toolchain_channel: ${{ matrix.rust_version }}
components: clippy
- name: Install deps
run: |
sudo apt install gettext
- name: cargo clippy
run: cargo clippy --workspace --all-targets ${{ matrix.features }} -- --deny=warnings
rustdoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/rust-toolchain@stable
- name: Install deps
run: |
sudo apt install gettext
- name: cargo doc
run: |
RUSTDOCFLAGS='-D warnings' cargo doc --workspace
- name: cargo doctest
run: |
cargo test --doc --workspace

View File

@@ -12,7 +12,6 @@ permissions:
jobs:
lock:
if: github.repository_owner == 'fish-shell'
permissions:
issues: write # for dessant/lock-threads to lock issues
pull-requests: write # for dessant/lock-threads to lock PRs

42
.github/workflows/mac_codesign.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: macOS build and codesign
on:
workflow_dispatch: # Enables manual trigger from GitHub UI
jobs:
build-and-code-sign:
runs-on: macos-latest
environment: macos-codesign
steps:
- uses: actions/checkout@v4
- name: Install Rust 1.73.0
uses: dtolnay/rust-toolchain@1.73.0
with:
targets: x86_64-apple-darwin
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: build-and-codesign
run: |
cargo install apple-codesign
mkdir -p "$FISH_ARTEFACT_PATH"
echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode > /tmp/app.p12
echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode > /tmp/installer.p12
echo "$MACOS_NOTARIZE_JSON" > /tmp/notarize.json
./build_tools/make_pkg.sh -s -f /tmp/app.p12 -i /tmp/installer.p12 -p "$MAC_CODESIGN_PASSWORD" -n -j /tmp/notarize.json
rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json
env:
MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }}
MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }}
MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }}
MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }}
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
CARGO_NET_GIT_FETCH_WITH_CLI: true
FISH_ARTEFACT_PATH: /tmp/fish-built
- uses: actions/upload-artifact@v4
with:
name: macOS Artefacts
path: /tmp/fish-built/*
if-no-files-found: error

View File

@@ -1,9 +1,9 @@
name: Test
name: make fish_run_tests
on: [push, pull_request]
env:
FISH_TEST_MAX_CONCURRENCY: "4"
CTEST_PARALLEL_LEVEL: "4"
CMAKE_BUILD_PARALLEL_LEVEL: "4"
permissions:
@@ -11,16 +11,16 @@ permissions:
jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- uses: dtolnay/rust-toolchain@1.70
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_sphinx: true
- name: Generate a locale that uses a comma as decimal separator.
run: |
sudo apt install gettext libpcre2-dev python3-pexpect python3-sphinx tmux
# Generate a locale that uses a comma as decimal separator.
sudo locale-gen fr_FR.UTF-8
- name: cmake
run: |
@@ -32,30 +32,31 @@ jobs:
- name: make fish_run_tests
run: |
make -C build VERBOSE=1 fish_run_tests
- uses: dtolnay/rust-toolchain@stable
- name: translation updates
run: |
# Required for our custom xgettext implementation.
cargo install --locked --version 1.0.106 cargo-expand
# 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
PATH="$PWD/build:$PATH" build_tools/update_translations.fish --no-mo
# 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; }
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 --no-mo` after changing source files.'; exit 1; }
ubuntu-32bit-static-pcre2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- uses: dtolnay/rust-toolchain@1.70
with:
targets: "i586-unknown-linux-gnu"
targets: "i586-unknown-linux-gnu" # rust-toolchain wants this comma-separated
- 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
sudo apt update
sudo apt install gettext python3-pexpect g++-multilib tmux
- name: cmake
env:
CFLAGS: "-m32"
@@ -70,6 +71,7 @@ jobs:
make -C build VERBOSE=1 fish_run_tests
ubuntu-asan:
runs-on: ubuntu-latest
env:
# Rust has two different memory sanitizers of interest; they can't be used at the same time:
@@ -79,6 +81,7 @@ jobs:
#
RUSTFLAGS: "-Zsanitizer=address"
# RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
steps:
- uses: actions/checkout@v4
# All -Z options require running nightly
@@ -88,11 +91,8 @@ jobs:
# this is comma-separated
components: rust-src
- name: Install deps
uses: ./.github/actions/install-dependencies
with:
include_sphinx: false
- name: Install llvm
run: |
sudo apt install gettext libpcre2-dev python3-pexpect tmux
sudo apt install llvm # for llvm-symbolizer
- name: cmake
env:
@@ -121,53 +121,58 @@ jobs:
export LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$PWD/build_tools/lsan_suppressions.txt"
make -C build VERBOSE=1 fish_run_tests
# Our clang++ tsan builds are not recognizing safe rust patterns (such as the fact that Drop
# cannot be called while a thread is using the object in question). Rust has its own way of
# running TSAN, but for the duration of the port from C++ to Rust, we'll keep this disabled.
# ubuntu-threadsan:
#
# runs-on: ubuntu-latest
#
# steps:
# - uses: actions/checkout@v4
# - uses: dtolnay/rust-toolchain@1.70
# - name: Install deps
# run: |
# sudo apt install gettext libpcre2-dev python3-pexpect tmux
# - name: cmake
# env:
# FISH_CI_SAN: 1
# CC: clang
# run: |
# mkdir build && cd build
# cmake ..
# - name: make
# run: |
# make
# - name: make fish_run_tests
# run: |
# make -C build fish_run_tests
macos:
runs-on: macos-latest
env:
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# 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@v4
- uses: ./.github/actions/rust-toolchain@oldest-supported
- uses: dtolnay/rust-toolchain@1.70
- name: Install deps
run: |
# --break-system-packages because homebrew has now declared itself "externally managed".
# this is CI so we don't actually care.
sudo pip3 install --break-system-packages pexpect
brew install gettext tmux
brew install tmux
- name: cmake
run: |
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake -DWITH_GETTEXT=NO -DCMAKE_BUILD_TYPE=Debug ..
- name: make
run: |
make -C build VERBOSE=1
- name: make fish_run_tests
run: |
make -C build VERBOSE=1 fish_run_tests
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
update: true
msystem: MSYS
- name: Install deps
# Not using setup-msys2 `install` option to make it easier to copy/paste
run: |
pacman --noconfirm -S --needed git rust
- name: cargo build
run: |
cargo build
- name: smoketest
# We can't use `cargo test` yet, there are just too many failures
# so this is just a quick check to make sure that fish can swim
run: |
set -x
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]

View File

@@ -1,191 +0,0 @@
name: Create a new release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (tag name)'
required: true
type: string
permissions:
contents: write
jobs:
is-release-tag:
name: Pre-release checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Check if the pushed tag looks like a release
run: |
set -x
commit_subject=$(git log -1 --format=%s)
tag=$(git describe)
[ "$commit_subject" = "Release $tag" ]
source-tarball:
needs: [is-release-tag]
name: Create the source tarball
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
tarball-name: ${{ steps.version.outputs.tarball-name }}
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Install dependencies
run: sudo apt install cmake gettext ninja-build python3-pip python3-sphinx
- uses: ./.github/actions/install-sphinx-markdown-builder
- name: Create tarball
run: |
set -x
mkdir /tmp/fish-built
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
relnotes=/tmp/fish-built/release-notes.md
# Need history since the last release (i.e. tag) for stats.
git fetch --tags
git fetch --unshallow
sh -x ./build_tools/release-notes.sh >"$relnotes"
# Delete title
sed -n 1p "$relnotes" | grep -q "^## fish .*"
sed -n 2p "$relnotes" | grep -q '^$'
sed -i 1,2d "$relnotes"
- name: Upload tarball artifact
uses: actions/upload-artifact@v4
with:
name: source-tarball
path: |
/tmp/fish-built/fish-${{ inputs.version }}.tar.xz
/tmp/fish-built/release-notes.md
if-no-files-found: error
packages-for-linux:
needs: [is-release-tag]
name: Build single-file fish for Linux (experimental)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Install Rust Stable
uses: ./.github/actions/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl
- name: Install dependencies
run: sudo apt install crossbuild-essential-arm64 gettext musl-tools python3-sphinx
- name: Build statically-linked executables
run: |
set -x
cargo build --release --target x86_64-unknown-linux-musl --bin fish
CFLAGS="-D_FORTIFY_SOURCE=2" \
CC=aarch64-linux-gnu-gcc \
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-lgcc -C link-arg=-D_FORTIFY_SOURCE=0" \
cargo build --release --target aarch64-unknown-linux-musl --bin fish
- name: Compress
run: |
set -x
for arch in x86_64 aarch64; do
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
-C target/$arch-unknown-linux-musl/release fish
done
- uses: actions/upload-artifact@v4
with:
name: Static builds for Linux
path: fish-${{ inputs.version }}-linux-*.tar.xz
if-no-files-found: error
create-draft-release:
needs:
- is-release-tag
- source-tarball
- packages-for-linux
name: Create release draft
runs-on: ubuntu-latest
steps:
- 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@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@v2
with:
tag_name: ${{ inputs.version }}
name: fish ${{ inputs.version }}
body_path: /tmp/artifacts/release-notes.md
draft: true
files: |
/tmp/artifacts/fish-${{ inputs.version }}.tar.xz
/tmp/artifacts/fish-${{ inputs.version }}-linux-*.tar.xz
packages-for-macos:
needs: [is-release-tag, create-draft-release]
name: Build packages for macOS
runs-on: macos-latest
environment: macos-codesign
steps:
- uses: actions/checkout@v4
with:
# Workaround for https://github.com/actions/checkout/issues/882
ref: ${{ inputs.version }}
- name: Install Rust
uses: ./.github/actions/rust-toolchain@oldest-supported
with:
targets: x86_64-apple-darwin
- name: Install Rust Stable
uses: ./.github/actions/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Install dependencies
run: brew install gettext
- name: Build and codesign
run: |
die() { echo >&2 "$*"; exit 1; }
[ -n "$MAC_CODESIGN_APP_P12_BASE64" ] || die "Missing MAC_CODESIGN_APP_P12_BASE64"
[ -n "$MAC_CODESIGN_INSTALLER_P12_BASE64" ] || die "Missing MAC_CODESIGN_INSTALLER_P12_BASE64"
[ -n "$MAC_CODESIGN_PASSWORD" ] || die "Missing MAC_CODESIGN_PASSWORD"
[ -n "$MACOS_NOTARIZE_JSON" ] || die "Missing MACOS_NOTARIZE_JSON"
set -x
export FISH_ARTEFACT_PATH=/tmp/fish-built
# macOS runners keep having issues loading Cargo.toml dependencies from git (GitHub) instead
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
cargo install apple-codesign
mkdir -p "$FISH_ARTEFACT_PATH"
echo "$MAC_CODESIGN_APP_P12_BASE64" | base64 --decode >/tmp/app.p12
echo "$MAC_CODESIGN_INSTALLER_P12_BASE64" | base64 --decode >/tmp/installer.p12
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
version=$(git describe)
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.app.zip" ]
[ -f "${FISH_ARTEFACT_PATH}/fish-$version.pkg" ]
rm /tmp/installer.p12 /tmp/app.p12 /tmp/notarize.json
env:
MAC_CODESIGN_APP_P12_BASE64: ${{ secrets.MAC_CODESIGN_APP_P12_BASE64 }}
MAC_CODESIGN_INSTALLER_P12_BASE64: ${{ secrets.MAC_CODESIGN_INSTALLER_P12_BASE64 }}
MAC_CODESIGN_PASSWORD: ${{ secrets.MAC_CODESIGN_PASSWORD }}
MACOS_NOTARIZE_JSON: ${{ secrets.MACOS_NOTARIZE_JSON }}
- name: Add macOS packages to the release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(git describe)
gh release upload $version \
/tmp/fish-built/fish-$version.app.zip \
/tmp/fish-built/fish-$version.pkg

41
.github/workflows/rust_checks.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Rust checks
on: [push, pull_request]
permissions:
contents: read
jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- name: cargo fmt
run: cargo fmt --check --all
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- name: Install deps
run: |
sudo apt install gettext libpcre2-dev
- name: cmake
run: |
cmake -B build
- name: cargo clippy
# This used to have --deny=warnings, but that turns rust release day
# into automatic CI failure day, so we don't do that.
run: cargo clippy --workspace --all-targets
# Disabling for now because it also checks "advisories",
# making CI fail for reasons unrelated to the patch
# cargo-deny:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: EmbarkStudios/cargo-deny-action@v1

80
.github/workflows/staticbuild.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: staticbuilds
on:
# release:
# types: [published]
# schedule:
# - cron: "14 13 * * *"
workflow_dispatch:
env:
CTEST_PARALLEL_LEVEL: "1"
CMAKE_BUILD_PARALLEL_LEVEL: "4"
jobs:
staticbuilds-linux:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: dtolnay/rust-toolchain@1.70
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prepare
run: |
sudo apt install python3-sphinx
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-musl
sudo apt install musl-tools crossbuild-essential-arm64 python3-pexpect tmux -y
- name: Build
run: |
CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2" CMAKE_WITH_GETTEXT=0 CC=aarch64-linux-gnu-gcc RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-lgcc -C link-arg=-D_FORTIFY_SOURCE=0" cargo build --release --target aarch64-unknown-linux-musl --bin fish
cargo build --release --target x86_64-unknown-linux-musl
- name: Test
run: |
test -e tests/test_driver.py && tests/test_driver.py -f /tmp target/x86_64-unknown-linux-musl/release/
- name: Compress
run: |
tar -cazf fish-static-x86_64-$(git describe).tar.xz -C target/x86_64-unknown-linux-musl/release/ fish
tar -cazf fish-static-aarch64-$(git describe).tar.xz -C target/aarch64-unknown-linux-musl/release/ fish
- uses: actions/upload-artifact@v4
with:
name: fish-static-linux
path: |
fish-*.tar.xz
retention-days: 14
staticbuilds-macos:
runs-on: macos-latest
permissions:
contents: read
steps:
- uses: dtolnay/rust-toolchain@1.70
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prepare
run: |
sudo pip3 install --break-system-packages sphinx
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
- name: Build
run: |
PCRE2_SYS_STATIC=1 cargo build --release --target aarch64-apple-darwin --bin fish
PCRE2_SYS_STATIC=1 cargo build --release --target x86_64-apple-darwin --bin fish
- name: Compress
run: |
tar -cazf fish-macos-aarch64.tar.xz -C target/aarch64-apple-darwin/release/ fish
tar -cazf fish-macos-x86_64.tar.xz -C target/x86_64-apple-darwin/release/ fish
- uses: actions/upload-artifact@v4
with:
name: fish-static-macos
path: |
fish-macos-*.tar.xz
retention-days: 14

6
.gitignore vendored
View File

@@ -38,6 +38,8 @@ Desktop.ini
Thumbs.db
ehthumbs.db
po/template.po
*.mo
.directory
.fuse_hidden*
@@ -76,7 +78,6 @@ __pycache__
/share/__fish_build_paths.fish
/share/pkgconfig
/tests/*.tmp.*
/tests/.last-check-all-files
# xcode
## Build generated
@@ -105,6 +106,3 @@ target/
# JetBrains editors.
.idea/
# AI slop
.claude/

View File

@@ -1,276 +1,92 @@
fish ?.?.? (released ???)
fish 4.1.0 (released ???)
=========================
fish 4.1.3 (released ???)
=========================
This release fixes the following regressions identified in 4.1.0:
- Crash on invalid :doc:`function <cmds/function>` command (:issue:`11912`).
as well as the following regressions identified in 4.0.0:
- Crash when passing negative PIDs to :doc:`wait <cmds/wait>` (:issue:`11929`).
fish 4.1.2 (released October 7, 2025)
=====================================
This release fixes the following regressions identified in 4.1.0:
- Fixed spurious error output when completing remote file paths for ``scp`` (:issue:`11860`).
- Fixed the :kbd:`alt-l` binding not formatting ``ls`` output correctly (one entry per line, no colors) (:issue:`11888`).
- Fixed an issue where focus events (currently only enabled in ``tmux``) would cause multiline prompts to be redrawn in the wrong line (:issue:`11870`).
- Stopped printing output that would cause a glitch on old versions of Midnight Commander (:issue:`11869`).
- Added a fix for some configurations of Zellij where :kbd:`escape` key processing was delayed (:issue:`11868`).
- Fixed a case where the :doc:`web-based configuration tool <cmds/fish_config>` would generate invalid configuration (:issue:`11861`).
- Fixed a case where pasting into ``fish -c read`` would fail with a noisy error (:issue:`11836`).
- Fixed a case where upgrading fish would break old versions of fish that were still running.
In general, fish still needs to be restarted after it is upgraded,
except for `standalone builds <https://github.com/fish-shell/fish-shell/?tab=readme-ov-file#building-fish-with-embedded-data-experimental>`__.
fish 4.1.1 (released September 30, 2025)
========================================
This release fixes the following regressions identified in 4.1.0:
- Many of our new Chinese translations were more confusing than helpful; they have been fixed or removed (:issue:`11833`).
Note that you can work around this type of issue by configuring fish's :doc:`message localization <cmds/_>`:
if your environment contains something like ``LANG=zh_CN.UTF-8``,
you can use ``set -g LC_MESSAGES en`` to use English messages inside fish.
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`).
- 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
.. 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.
- fish now supports transient prompts: if :envvar:`fish_transient_prompt` is set to 1, fish will reexecute prompt functions with the ``--final-rendering`` argument before running a commandline (:issue:`11153`).
- Tab completion results are truncated up to the common directory path, instead of somewhere inside that path. E.g. if you complete "share/functions", and it includes the files "foo.fish" and "bar.fish",
the completion pager will now show "…/foo.fish" and "…/bar.fish" (:issue:`11250`).
- Self-installing builds as created by e.g. ``cargo install`` no longer install other files, see :ref:`below <changelog-4.1-embedded>`.
- Our gettext-based message-localization has been reworked,
adding translations to self-installing builds; see :ref:`below <changelog-4.1-gettext>`.
- Compound commands (``begin; echo 1; echo 2; end``) can now be now be abbreviated using braces (``{ echo1; echo 2 }``), like in other shells.
- Fish now supports transient prompts: if :envvar:`fish_transient_prompt` is set to 1, fish will reexecute prompt functions with the ``--final-rendering`` argument before running a commandline (:issue:`11153`).
- When tab completion results are truncated, any common directory name is omitted. E.g. if you complete "share/functions", and it includes the files "foo.fish" and "bar.fish",
the completion pager will now show "…/foo.fish" and "…/bar.fish". This will make the candidates shorter and allow for more to be shown at once (:issue:`11250`).
- The self-installing configuration introduced in fish 4.0 has been changed.
Now fish built with embedded data will just read the data straight from its own binary or write it out when necessary, instead of requiring an installation step on start.
That means it is now possible to build fish as a single file and copy it to a compatible system, including as a different user, without extracting any files.
As before this is the default when building via `cargo`, and disabled when building via `cmake`, and for packagers we continue to recommend cmake.
Note: When fish is built like this, the `$__fish_data_dir` variable will be empty because that directory no longer has meaning. If you need to load files from there,
use `status get-file` or find alternatives (like loading completions for "foo" via `complete -C"foo "`).
We're considering making data embedding mandatory in future releases because it has a few advantages even for installation from a package (like making file conflicts with other packages impossible). (:issue:`11143`)
Deprecations and removed features
---------------------------------
- ``set_color --background=COLOR`` no longer implicitly activates bold mode.
If your theme is stored in universal variables (the historical default), some bold formatting might be lost.
To fix this, we suggest updating to the latest version of our theme, to explicitly activate bold mode,
for example use ``fish_config theme save "fish default"``.
- ``{echo,echo}`` or ``{ echo, echo }`` are no longer interpreted as brace expansion tokens but as :doc:`compound commands <cmds/begin>`.
- Terminfo-style key names (``bind -k nul``) are no longer supported. They had been superseded by fish's :doc:`own key names <cmds/bind>` since 4.0 (:issue:`11342`).
- fish no longer reads the terminfo database, so its behavior is generally no longer affected by the :envvar:`TERM` environment variable (:issue:`11344`).
For the time being, this change can be reversed via the ``ignore-terminfo`` :ref:`feature flag <featureflags>`.
To do so, run the following once and restart fish::
- Tokens like ``{echo,echo}`` or ``{ echo, echo }`` in command position are no longer interpreted as brace expansion but as compound command.
- Terminfo-style key names (``bind -k``) are no longer supported. They had been superseded by the native notation since 4.0,
and currently they would map back to information from terminfo, which does not match what terminals would send with the kitty keyboard protocol (:issue:`11342`).
- fish no longer reads the terminfo database, so its behavior is no longer affected by the :envvar:`TERM` environment variable (:issue:`11344`).
For the time being, this can be turned off via the "ignore-terminfo" feature flag::
set -Ua fish_features no-ignore-terminfo
- The ``--install`` option when fish is built as self-installing is removed, see :ref:`below <changelog-4.1-embedded>`.
- ``set_color ff0000`` now outputs 24-bit RGB true-color even if :envvar:`COLORTERM` is unset.
One can override this by setting :envvar:`fish_term24bit` to 0 (:issue:`11372`).
- fish now requires the terminal to respond to queries for the :ref:`Primary Device Attribute <term-compat-primary-da>`.
For now, this can be reversed via a :ref:`feature flag <featureflags>`,
by running (once) ``set -Ua fish_features no-query-term`` and restarting fish.
- Users of GNU screen may experience :ref:`minor glitches <term-compat-dcs-gnu-screen>` when starting fish.
- The ``--install`` option when fish is built as self-installable was removed. If you need to write out fish's data you can use the new ``status list-files`` and ``status get-file`` subcommands, but it should no longer be necessary. (:issue:`11143`)
- RGB colors (``set_color ff0000``) now default to using 24-bit RGB true-color commands, even if $COLORTERM is unset, because that is often lost e.g. over ssh (:issue:`11372`)
- To go back to using the nearest match from the 256-color palette, use ``set fish_term24bit 0`` or set $COLORTERM to a value that is not "24bit" or "truecolor".
To make the nearest-match logic use the 16 color palette instead, use ``set fish_term256 0``.
- Inside macOS Terminal.app, fish makes an attempt to still use the palette colors.
If that doesn't work, use ``set fish_term24bit 0``.
- ``set_color --background=COLOR`` no longer implicitly activates bold mode.
To mitigate this change on existing installations that use a default theme, update your theme with ``fish_config theme choose`` or ``fish_config theme save``.
Scripting improvements
----------------------
- The :doc:`argparse <cmds/argparse>` builtin has seen many improvements, see :ref:`below <changelog-4.1-argparse>`.
- The :doc:`string pad <cmds/string-pad>` command now has a ``-C/--center`` option.
- The :doc:`psub <cmds/psub>` command now allows combining ``--suffix`` with ``--fifo`` (:issue:`11729`).
- The :doc:`read <cmds/read>` builtin has learned the ``--tokenize-raw`` option to tokenize without quote removal (:issue:`11084`).
Interactive improvements
------------------------
- Autosuggestions are now also provided in multi-line command lines. Like :kbd:`ctrl-r`, these operate only on the current line.
- Autosuggestions used to not suggest multi-line command-lines from history; now autosuggestions include individual lines from multi-line command-lines.
- The history pager search now preserves ordering between :kbd:`ctrl-s` forward and :kbd:`ctrl-r` backward searches.
- Instead of highlighting events by flashing *all text to the left of the cursor*,
failing history token search (:kbd:`alt-.`) flashes the associated token,
failing tab-completion flashes the to-be-completed token (:issue:`11050`),
deleting an autosuggestion (:kbd:`shift-delete`) flashes the suggestion,
and all other scenarios flash the full command line.
- Pasted commands are now stripped of any :code:`$\ ` command prefixes, to help pasting code snippets.
- Builtin help options (e.g. ``abbr --help``) now use ``man`` directly, meaning that variables like :envvar:`MANWIDTH` are respected (:issue:`11786`).
- ``funced`` will now edit copied functions directly, instead of the file where ``function --copy`` was invoked. (:issue:`11614`)
- Added a simple ``fish_jj_prompt`` which reduces visual noise in the prompt inside `Jujutsu <https://jj-vcs.github.io/jj/latest/>`__ repositories that are colocated with Git.
- Autosuggestions are now also provided in multi-line command lines. Like `ctrl-r`, autosuggestions operate only on the current line.
- Autosuggestions used to not suggest multi-line commandlines from history; now autosuggestions include individual lines from multi-line command lines.
- The history search now preserves ordering between :kbd:`ctrl-s` forward and :kbd:`ctrl-r` backward searches.
- Left mouse click (as requested by `click_events <terminal-compatibility.html#click-events>`__) can now select pager items (:issue:`10932`).
- Instead of flashing all the text to the left of the cursor, fish now flashes the matched token during history token search, the completed token during completion (:issue:`11050`), the autosuggestion when deleting it, and the full command line in all other cases.
- Pasted commands are now stripped of any ``$`` prefix.
New or improved bindings
^^^^^^^^^^^^^^^^^^^^^^^^
- On non-macOS systems, :kbd:`alt-left`, :kbd:`alt-right`, :kbd:`alt-backspace` and :kbd:`alt-delete` no longer operate on punctuation-delimited words but on whole arguments, possibly including special characters like ``/`` and quoted spaces.
- On non-macOS systems, :kbd:`alt-left`, :kbd:`alt-right`, :kbd:`alt-backspace`, :kbd:`alt-delete` no longer operate on punctuation-delimited words but on whole arguments, possibly including special characters like ``/`` and quoted spaces.
On macOS, the corresponding :kbd:`ctrl-` prefixed keys operate on whole arguments.
Word operations are still available via the other respective modifier, just like in most web browsers.
Word operations are still available via the other respective modifier, same as in the browser.
- :kbd:`ctrl-z` (undo) after executing a command will restore the previous cursor position instead of placing the cursor at the end of the command line.
- The :kbd:`alt-s` binding will now also use ``run0`` if available.
- Some mouse support has been added: the OSC 133 prompt marking feature has learned about kitty's ``click_events=1`` flag, which allows moving fish's cursor by clicking in the command line,
and selecting pager items (:issue:`10932`).
- Before clearing the screen and redrawing, :kbd:`ctrl-l` now pushes all text located above the prompt to the terminal's scrollback,
via a new special input function :ref:`scrollback-push <special-input-functions-scrollback-push>`.
For compatibility with terminals that do not implement ECMA-48's :ref:`SCROLL UP <term-compat-indn>` command,
this function is only used if the terminal advertises support for that via :ref:`XTGETTCAP <term-compat-xtgettcap>`.
- Vi mode has learned :kbd:`ctrl-a` (increment) and :kbd:`ctrl-x` (decrement) (:issue:`11570`).
- The OSC 133 prompt marking feature has learned about kitty's ``click_events=1`` flag, which allows moving fish's cursor by clicking.
- :kbd:`ctrl-l` now pushes all text located above the prompt to the terminal's scrollback, before clearing and redrawing the screen (via a new special input function ``scrollback-push``).
For compatibility with terminals that do not provide the scroll-forward command,
this is only enabled by default if the terminal advertises support for the ``indn`` capability via XTGETTCAP.
- Bindings using shift with non-ASCII letters (such as :kbd:`ctrl-shift-ä`) are now supported.
If there is any modifier other than shift, this is the recommended notation (as opposed to :kbd:`ctrl-Ä`).
Completions
^^^^^^^^^^^
- ``git`` completions now show the remote URL as description when completing remotes.
- ``systemctl`` completions no longer print escape codes if ``SYSTEMD_COLORS`` happens to be set (:issue:`11465`).
- Added and improved many completion scripts, notably ``tmux``.
- ``git`` completions now show the remote url as a description when completing remotes.
- ``systemctl`` completions no longer print escape codes if ``SYSTEMD_COLORS`` is set (:issue:`11465`).
Improved terminal support
^^^^^^^^^^^^^^^^^^^^^^^^^
- Support for double, curly, dotted and dashed underlines, for use in ``fish_color_*`` variables and the :doc:`set_color builtin <cmds/set_color>` (:issue:`10957`).
- Support for double, curly, dotted and dashed underlines in `fish_color_*` variables and :doc:`set_color <cmds/set_color>` (:issue:`10957`).
- Underlines can now be colored independent of text (:issue:`7619`).
- New documentation page :doc:`Terminal Compatibility <terminal-compatibility>` (also accessible via ``man fish-terminal-compatibility``) lists the terminal control sequences used by fish.
- New documentation page `Terminal Compatibility <terminal-compatibility.html>`_ (also accessible via ``man fish-terminal-compatibility``) lists required and optional terminal control sequences used by fish.
Other improvements
------------------
- Updated Chinese and German translations.
- ``fish_indent`` and ``fish_key_reader`` are now available as builtins, and if fish is called with that name it will act like the given tool (as a multi-call binary).
This allows truly distributing fish as a single file. (:issue:`10876`)
- ``fish_indent --dump-parse-tree`` now emits simple metrics about the tree including its memory consumption.
- We added some tools to improve development workflows, for example ``build_tools/{check,update_translations,release}.sh`` and ``tests/test_driver.py``.
In conjunction with ``cargo``, these enable almost all day-to-day development tasks without using CMake.
For distributors
----------------
- Builtin commands that support the ``--help`` option now require the ``man`` program.
The direct dependency on ``mandoc`` and ``nroff`` has been removed.
- fish no longer uses gettext MO files, see :ref:`below <changelog-4.1-gettext>`.
If you have use cases which are incompatible with our new approach, please let us know.
- The :doc:`fish_indent <cmds/fish_indent>` and :doc:`fish_key_reader <cmds/fish_key_reader>` programs are now also available as builtins.
If fish is invoked via e.g. a symlink with one of these names,
it will act like the given tool (i.e. it's a multi-call binary).
This allows truly distributing fish as a single file (:issue:`10876`).
- The CMake build configuration has been simplified and no longer second-guesses rustup.
It will run rustc and cargo via :envvar:`PATH` or in ~/.cargo/bin/.
If that doesn't match your setup, set the Rust_COMPILER and Rust_CARGO CMake variables (:issue:`11328`).
- Cygwin support has been reintroduced, since `Rust gained a Cygwin target <https://github.com/rust-lang/rust/pull/134999>`__ (:issue:`11238`).
- CMake 3.15 is now required.
.. _changelog-4.1-embedded:
Changes to self-installing builds
---------------------------------
The self-installing build type introduced in fish 4.0 has been changed (:issue:`11143`).
Now fish built with embedded data will just read the data straight from its own binary or write it out to temporary files when necessary, instead of requiring an installation step on start.
That means it is now possible to build fish as a single file and copy it to any system with a compatible CPU architecture, including as a different user, without extracting any files.
As before, this is the default when building via ``cargo``, and disabled when building via CMake.
For packagers we continue to recommend CMake.
Note: When fish is built like this, the :envvar:`__fish_data_dir` variable will be empty because that directory no longer has meaning.
You should generally not need these files.
For example, if you want to make sure that completions for "foo" are loaded, use ``complete -C"foo " >/dev/null`` instead).
The raw files are still exposed via :ref:`status subcommands <status-get-file>`, mainly for fish's internal use, but you can also use them as a last resort.
Remaining benefits of a full installation (as currently done by CMake) are:
- man pages like ``fish(1)`` in standard locations, easily accessible from outside fish.
- a local copy of the HTML documentation, typically accessed via the :doc:`help <cmds/help>` function.
In builds with embedded data, ``help`` will redirect to e.g. `<https://fishshell.com/docs/current/>`__
- ``fish_indent`` and ``fish_key_reader`` as separate files, making them easily accessible outside fish
- an (empty) ``/etc/fish/config.fish`` as well as empty directories ``/etc/fish/{functions,completions,conf.d}``
- ``$PREFIX/share/pkgconfig/fish.pc``, which defines directories for configuration-snippets, like ``vendor_completions.d``
.. _changelog-4.1-gettext:
Changes to gettext localization
-------------------------------
We replaced several parts of the gettext functionality with custom implementations (:issue:`11726`).
Most notably, message extraction, which should now work reliably, and the runtime implementation, where we no longer dynamically link to gettext, but instead use our own implementation, whose behavior is similar to GNU gettext, with some :doc:`minor deviations <cmds/_>`.
Our implementation now fully respects fish variables, so locale variables do not have to be exported for fish localizations to work.
They still have to be exported to inform other programs about language preferences.
The :envvar:`LANGUAGE` environment variable is now treated as a path variable, meaning it is an implicitly colon-separated list.
While we no longer have any runtime dependency on gettext, we still need gettext tools for building, most notably ``msgfmt``.
When building without ``msgfmt`` available, localization will not work with the resulting executable.
Localization data is no longer sourced at runtime from MO files on the file system, but instead built into the executable.
This is always done, independently of the other data embedding, so all fish executables will have access to all message catalogs, regardless of the state of the file system.
Disabling our new ``localize-messages`` cargo feature will cause fish to be built without localization support.
CMake builds can continue to use the ``WITH_GETTEXT`` option, with the same semantics as the ``localize-messages`` feature.
The current implementation does not provide any configuration options for controlling which language catalogs are built into the executable (other than disabling them all).
As a workaround, you can delete files in the ``po`` directory before building to exclude unwanted languages.
.. _changelog-4.1-argparse:
Changes to the :doc:`argparse <cmds/argparse>` builtin
------------------------------------------------------
- ``argparse`` now saves recognised options, including option-arguments in :envvar:`argv_opts`, allowing them to be forwarded to other commands (:issue:`6466`).
- ``argparse`` options can now be marked to be deleted from :envvar:`argv_opts` (by adding a ``&`` at the end of the option spec, before a ``!`` if present). There is now also a corresponding ``-d`` / ``--delete`` option to ``fish_opt``.
- ``argparse --ignore-unknown`` now removes preceding known short options from groups containing unknown options (e.g. when parsing ``-abc``, if ``a`` is known but ``b`` is not, then :envvar:`argv` will contain ``-bc``).
- ``argparse`` now has an ``-u`` / ``--move-unknown`` option that works like ``--ignore-unknown`` but preserves unknown options in :envvar:`argv`.
- ``argparse`` now has an ``-S`` / ``--strict-longopts`` option that forbids abbreviating long options or passing them with a single dash (e.g. if there is a long option called ``foo``, ``--fo`` and ``--foo`` won't match it).
- ``argparse`` now has a ``-U`` / ``--unknown-arguments`` option to specify how to parse unknown option's arguments.
- ``argparse`` now allows specifying options that take multiple optional values by using ``=*`` in the option spec (:issue:`8432`).
In addition, ``fish_opt`` has been modified to support such options by using the ``--multiple-vals`` together with ``-o`` / ``--optional-val``; ``-m`` is also now acceptable as an abbreviation for ``--multiple-vals``.
- ``fish_opt`` no longer requires you give a short flag name when defining options, provided you give it a long flag name with more than one character.
- ``argparse`` option specifiers for long-only options can now start with ``/``, allowing the definition of long options with a single letter. Due to this change, the ``--long-only`` option to ``fish_opt`` is now no longer necessary and is deprecated.
- ``fish_opt`` now has a ``-v`` / ``--validate`` option you can use to give a fish script to validate values of the option.
--------------
fish 4.0.9 (released September 27, 2025)
========================================
This release fixes:
- a regression in 4.0.6 causing shifted keys to not be inserted on some terminals (:issue:`11813`).
- a regression in 4.0.6 causing the build to fail on systems where ``char`` is unsigned (:issue:`11804`).
- a regression in 4.0.0 causing a crash on an invalid :doc:`bg <cmds/bg>` invocation.
--------------
fish 4.0.8 (released September 18, 2025)
========================================
This release fixes a regression in 4.0.6 that caused user bindings to be shadowed by either fish's or a plugin's bindings (:issue:`11803`).
--------------
fish 4.0.6 (released September 12, 2025)
========================================
This release of fish fixes a number of issues identified in fish 4.0.2:
- fish now properly inherits $PATH under Windows WSL2 (:issue:`11354`).
- Remote filesystems are detected properly again on non-Linux systems.
- the :doc:`printf <cmds/printf>` builtin no longer miscalculates width of multi-byte characters (:issue:`11412`).
- For many years, fish has been "relocatable" -- it was possible to move the entire ``CMAKE_INSTALL_PREFIX`` and fish would use paths relative to its binary.
Only gettext locale paths were still determined purely at compile time, which has been fixed.
- the :doc:`commandline <cmds/commandline>` builtin failed to print the commandline set by a ``commandline -C`` invocation, which broke some completion scripts.
This has been corrected (:issue:`11423`).
- To work around terminals that fail to parse Operating System Command (OSC) sequences, a temporary feature flag has been added.
It allows you to disable prompt marking (OSC 133) by running (once) ``set -Ua fish_features no-mark-prompt`` and restarting fish (:issue:`11749`).
- The routines to save history and universal variables have seen some robustness improvements.
- builtin :doc:`status current-command <cmds/status>` no longer prints a trailing blank line.
- A crash displaying multi-line quoted command substitutions has been fixed (:issue:`11444`).
- Commands like ``set fish_complete_path ...`` accidentally disabled completion autoloading, which has been corrected.
- ``nmcli`` completions have been fixed to query network information dynamically instead of only when completing the first time.
- Git completions no longer print an error when no `git-foo` executable is in :envvar:`PATH`.
- Custom completions like ``complete foo -l long -xa ...`` that use the output of ``commandline -t``.
on a command-line like ``foo --long=`` have been invalidated by a change in 4.0; the completion scripts have been adjusted accordingly (:issue:`11508`).
- Some completions were misinterpreted, which caused garbage to be displayed in the completion list. This has been fixed.
- fish no longer interprets invalid control sequences from the terminal as if they were :kbd:`alt-[` or :kbd:`alt-o` key strokes.
- :doc:`bind <cmds/bind>` has been taught about the :kbd:`printscreen` and :kbd:`menu` keys.
- :kbd:`alt-delete` now deletes the word right of the cursor.
- :kbd:`ctrl-alt-h` erases the last word again (:issue:`11548`).
- :kbd:`alt-left` :kbd:`alt-right` were misinterpreted because they send unexpected sequences on some terminals; a workaround has been added. (:issue:`11479`).
- Key bindings like ``bind shift-A`` are no longer accepted; use ``bind shift-a`` or ``bind A``.
- Key bindings like ``bind shift-a`` take precedence over ``bind A`` when the key event included the shift modifier.
- Bindings using shift with non-ASCII letters (such as :kbd:`ctrl-shift-ä`) are now supported.
- Bindings with modifiers such as ``bind ctrl-w`` work again on non-Latin keyboard layouts such as a Russian one.
This is implemented by allowing key events such as :kbd:`ctrl-ц` to match bindings of the corresponding Latin key, using the kitty keyboard protocol's base layout key (:issue:`11520`).
- Vi mode: The cursor position after pasting via :kbd:`p` has been corrected.
- Vi mode: Trying to replace the last character via :kbd:`r` no longer replaces the last-but-one character (:issue:`11484`).
- ``fish_indent`` and ``fish_key_reader`` are still built as separate binaries for now, but can also be replaced with a symlink if you want to save disk space (:issue:`10876`).
- The CMake system was simplified and no longer second-guesses rustup. It will run rustc and cargo via $PATH or in ~/.cargo/bin/.
If that doesn't match your setup, set the Rust_COMPILER and Rust_CARGO cmake variables (:issue:`11328`).
- Cygwin support has been reintroduced, since rust gained a Cygwin target (https://github.com/rust-lang/rust/pull/134999, :issue:`11238`).
--------------
@@ -283,7 +99,7 @@ This release of fish fixes a number of issues identified in fish 4.0.1:
- The warning when the terminfo database can't be found has been downgraded to a log message. fish will act as if the terminal behaves like xterm-256color, which is correct for the vast majority of cases (:issue:`11277`, :issue:`11290`).
- Key combinations using the super (Windows/command) key can now (actually) be bound using the :kbd:`super-` prefix (:issue:`11217`). This was listed in the release notes for 4.0.1 but did not work correctly.
- :doc:`function <cmds/function>` is stricter about argument parsing, rather than allowing additional parameters to be silently ignored (:issue:`11295`).
- Using parentheses in the :doc:`test <cmds/test>` builtin works correctly, following a regression in 4.0.0 where they were not recognized (:issue:`11387`).
- Using parentheses in the :doc:`test <cmds/test>` builtin works correctly, following a regression in 4.0.0 where they were not recognized (:issue:`11387`).
- :kbd:`delete` in Vi mode when Num Lock is active will work correctly (:issue:`11303`).
- Abbreviations cannot alter the command-line contents, preventing a crash (:issue:`11324`).
- Improvements to various completions, including new completions for ``wl-randr`` (:issue:`11301`), performance improvements for ``cargo`` completions by avoiding network requests (:issue:`11347`), and other improvements for ``btrfs`` (:issue:`11320`), ``cryptsetup`` (:issue:`11315`), ``git`` (:issue:`11319`, :issue:`11322`, :issue:`11323`), ``jj`` (:issue:`11046`), and ``systemd-analyze`` (:issue:`11314`).
@@ -333,7 +149,7 @@ Notable backwards-incompatible changes
- As part of a larger binding rework, ``bind`` gained a new key notation.
In most cases the old notation should keep working, but in rare cases you may have to change a ``bind`` invocation to use the new notation.
See :ref:`below <changelog-4.0-new-bindings>` for details.
See :ref:`below <changelog-new-bindings>` for details.
- :kbd:`ctrl-c` now calls a new bind function called ``clear-commandline``. The old behavior, which leaves a "^C" marker, is available as ``cancel-commandline`` (:issue:`10935`)
- ``random`` will produce different values from previous versions of fish when used with the same seed, and will work more sensibly with small seed numbers.
The seed was never guaranteed to give the same result across systems,
@@ -355,7 +171,7 @@ Notable backwards-incompatible changes
Notable improvements and fixes
------------------------------
.. _changelog-4.0-new-bindings:
.. _changelog-new-bindings:
- fish now requests XTerm's ``modifyOtherKeys`` keyboard encoding and `kitty keyboard protocol's <https://sw.kovidgoyal.net/kitty/keyboard-protocol/>`_ progressive enhancements (:issue:`10359`).
Depending on terminal support, this allows to binding more key combinations, including arbitrary combinations of modifiers :kbd:`ctrl`, :kbd:`alt` and :kbd:`shift`, and distinguishing (for example) :kbd:`ctrl-i` from :kbd:`tab`.
@@ -486,7 +302,7 @@ Interactive improvements
New or improved bindings
^^^^^^^^^^^^^^^^^^^^^^^^
- When the cursor is on a command that resolves to an executable script, :kbd:`alt-o` will now open that script in your editor (:issue:`10266`).
- During up-arrow history search, :kbd:`shift-delete` will delete the current search item and move to the next older item. Previously this was only supported in the history pager.
- During up-arrow history search, :kbd:`shift-delete` will delete the current search item and move to the next older item. Previously this was only supported in the history pager.
- :kbd:`shift-delete` will also remove the currently-displayed autosuggestion from history, and remove it as a suggestion.
- :kbd:`ctrl-Z` (also known as :kbd:`ctrl-shift-z`) is now bound to redo.
- Some improvements to the :kbd:`alt-e` binding which edits the command line in an external editor:
@@ -498,7 +314,7 @@ New or improved bindings
- Bindings like :kbd:`alt-l` that print output in between prompts now work correctly with multiline commandlines.
- :kbd:`alt-d` on an empty command line lists the directory history again. This restores the behavior of version 2.1.
- ``history-prefix-search-backward`` and ``-forward`` now maintain the cursor position, instead of moving the cursor to the end of the command line (:issue:`10430`).
- The following keys have refined behavior if the terminal supports :ref:`the new keyboard encodings <changelog-4.0-new-bindings>`:
- The following keys have refined behavior if the terminal supports :ref:`the new keyboard encodings <changelog-new-bindings>`:
- :kbd:`shift-enter` now inserts a newline instead of executing the command line.
- :kbd:`ctrl-backspace` now deletes the last word instead of only one character (:issue:`10741`).
@@ -557,7 +373,7 @@ Improved terminal support
Other improvements
------------------
- ``status`` gained a ``build-info`` subcommand, to print information on how fish was built, to help with debugging (:issue:`10896`).
- ``status`` gained a ``buildinfo`` subcommand, to print information on how fish was built, to help with debugging (:issue:`10896`).
- ``fish_indent`` will now collapse multiple empty lines into one (:issue:`10325`).
- ``fish_indent`` now preserves the modification time of files if there were no changes (:issue:`10624`).
- Performance in launching external processes has been improved for many cases (:issue:`10869`).

View File

@@ -15,6 +15,8 @@ endif()
# Set up standard directories.
include(GNUInstallDirs)
include(cmake/gettext.cmake)
# Set up PCRE2
# This sets an environment variable that needs to be available before the Rust stanzas
include(cmake/PCRE2.cmake)
@@ -52,8 +54,8 @@ function(CREATE_TARGET target)
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
--target ${Rust_CARGO_TARGET}
--no-default-features
--features=${FISH_CARGO_FEATURES}
${CARGO_FLAGS}
${FEATURES_ARG}
&&
"${CMAKE_COMMAND}" -E
copy "${rust_target_dir}/${rust_profile}/${target}" "${CMAKE_CURRENT_BINARY_DIR}"

View File

@@ -11,17 +11,10 @@ Contributions are welcome, and there are many ways to contribute!
Whether you want to change some of the core Rust source, enhance or add a completion script or function,
improve the documentation or translate something, this document will tell you how.
Getting Set Up
==============
Mailing List
============
Send patches to the public mailing list: mailto:~krobelus/fish-shell@lists.sr.ht.
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 developed 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::
@@ -36,7 +29,7 @@ For that, you'll require:
- Rust - when in doubt, try rustup
- CMake
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
- gettext (only the msgfmt tool) - optional, for translation support
- gettext (headers and libraries) - optional, for translation support
- Sphinx - optional, to build the documentation
Of course not everything is required always - if you just want to contribute something to the documentation you'll just need Sphinx,
@@ -50,7 +43,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 (``build_tools/check.sh``)
- Use automated tools to help you (including ``make fish_run_tests`` and ``build_tools/style.fish``)
Contributing completions
========================
@@ -110,7 +103,7 @@ before committing your change. That will run our autoformatters:
- ``rustfmt`` for Rust
- ``fish_indent`` (shipped with fish) for fish script
- ``ruff format`` for python
- ``black`` for python
If youve already committed your changes thats okay since it will then
check the files in the most recent commit. This can be useful after
@@ -226,10 +219,12 @@ Or you can run them on a fish, without involving cmake::
cargo build
cargo test # for the unit tests
tests/test_driver.py target/debug # for the script and interactive tests
tests/test_driver.py --cachedir=/tmp target/debug # for the script and interactive tests
Here, the first argument to test_driver.py refers to a directory with ``fish``, ``fish_indent`` and ``fish_key_reader`` in it.
In this example we're in the root of the git repo and have run ``cargo build`` without ``--release``, so it's a debug build.
The ``--cachedir /tmp`` argument means it will keep the fish_test_helper binary in /tmp instead of recompiling it for every test.
This saves some time, but isn't strictly necessary.
Git hooks
---------
@@ -250,42 +245,51 @@ One possibility is a pre-push hook script like this one:
# Git gives us lines like "refs/heads/frombranch SOMESHA1 refs/heads/tobranch SOMESHA1"
# We're only interested in the branches
isprotected=false
while read from _ to _; do
if [ "$to" = "refs/heads/$protected_branch" ]; then
isprotected=true
if [ "x$to" = "xrefs/heads/$protected_branch" ]; then
isprotected=1
fi
done
if "$isprotected"; then
echo "Running checks before push to master"
build_tools/check.sh
if [ "x$isprotected" = x1 ]; then
echo "Running tests before push to master"
make fish_run_tests
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "Tests failed for a push to master, we can't let you do that" >&2
exit 1
fi
fi
exit 0
This will check if the push is to the master branch and, if it is, only
allow the push if running ``build_tools/check.sh`` succeeds. In some circumstances
allow the push if running ``make fish_run_tests`` succeeds. In some circumstances
it may be advisable to circumvent this check with
``git push --no-verify``, but usually that isnt necessary.
To install the hook, place the code in a new file
``.git/hooks/pre-push`` and make it executable.
Coverity Scan
-------------
We use Coveritys static analysis tool which offers free access to open
source projects. While access to the tool itself is restricted,
fish-shell organization members should know that they can login
`here <https://scan.coverity.com/projects/fish-shell-fish-shell?tab=overview>`__
with their GitHub account. Currently, tests are triggered upon merging
the ``master`` branch into ``coverity_scan_master``. Even if you are not
a fish developer, you can keep an eye on our statistics there.
Contributing Translations
=========================
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 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.
Fish uses the GNU gettext library to translate messages from English to
other languages.
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.
stored in the ``po`` directory, named ``LANG.po``, where ``LANG`` is the
two letter ISO 639-1 language code of the target language (e.g. ``de`` for
German). A region specifier can also be used (e.g. ``pt_BR`` for Brazilian Portuguese).
Adding translations for a new language
--------------------------------------
@@ -293,38 +297,29 @@ Adding translations for a new language
Creating new translations requires the Gettext tools.
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
language.
In addition, the ``cargo-expand`` tool is required.
If you have ``cargo`` installed, run::
cargo install --locked --version 1.0.106 cargo-expand
to install ``cargo-expand`` (Note that other versions might not work correctly with our scripts).
To create a new translation, run::
build_tools/update_translations.fish po/ll_CC.po
build_tools/update_translations.fish po/LANG.po
This will create a new PO file containing all messages available for translation.
If the file already exists, it will be updated.
By default, this also creates ``mo`` files, which contain the information from the ``po`` files in a
binary format.
Fish uses these files for translating at runtime.
They are not tracked in version control, but they can help translators check if their translations
show up correctly.
If you build fish locally (``cargo build``), and then run the resulting binary,
it will make use of the ``mo`` files generated by the script.
Use the ``LANG`` environment variable to tell fish which language to use, e.g.::
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``).
It is important that the ``localize-messages`` cargo feature is enabled, which it is by default.
You can explicitly enable it using::
LANG=pt_BR.utf8 target/debug/fish
cargo build --features=localize-messages
Use environment variables to tell fish which language to use, e.g.::
LANG=pt_BR.utf8 fish
or within the running fish shell::
set LANG pt_BR.utf8
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
using the ``LANGUAGE`` variable, e.g.::
set LANGUAGE pt_BR de_DE
to try to translate messages to Portuguese, if that fails try German, and if that fails too you will
see the English version defined in the source code.
If you do not care about the ``mo`` files you can pass the ``--no-mo`` flag to the
``update_translations.fish`` script.
Modifying existing translations
-------------------------------
@@ -332,8 +327,13 @@ Modifying existing translations
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.
To see your translations in action you can run::
build_tools/update_translations.fish --only-mo po/LANG.po
to update the binary ``mo`` used by fish. Check the information for adding new languages for a
description on how you can get fish to use these files.
Running this script requires a fish executable and the gettext ``msgfmt`` tool.
Editing PO files
----------------
@@ -341,20 +341,20 @@ 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 ``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 ""
msgid "%ls: No suitable job\n"
msgstr ""
The ``msgid`` here is the "name" of the string to translate, typically the English string to translate.
The second line (``msgstr``) is where your translation goes.
For example::
msgid "%s: No suitable job\n"
msgstr "%s: Inget passande jobb\n"
msgid "%ls: No suitable job\n"
msgstr "%ls: 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 should have the same placeholders in the same order.
Any ``%s`` / ``%ls`` 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.
@@ -367,7 +367,7 @@ 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 ``#~``).
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 ``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.
@@ -381,7 +381,7 @@ macros:
::
streams.out.append(wgettext_fmt!("%s: There are no jobs\n", argv[0]));
streams.out.append(wgettext_fmt!("%ls: There are no jobs\n", argv[0]));
All messages in fish script must be enclosed in single or double quote
characters for our message extraction script to find them.
@@ -390,15 +390,15 @@ that the following are **not** valid:
::
echo (_ hello)
_ "goodbye"
echo (_ hello)
_ "goodbye"
Above should be written like this instead:
::
echo (_ "hello")
echo (_ "goodbye")
echo (_ "hello")
echo (_ "goodbye")
You can use either single or double quotes to enclose the
message to be translated. You can also optionally include spaces after

150
Cargo.lock generated
View File

@@ -42,9 +42,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
@@ -97,35 +97,20 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fish"
version = "4.1.0-snapshot"
version = "4.1.0-alpha0"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"errno",
"fish-build-helper",
"fish-build-man-pages",
"fish-gettext-extraction",
"fish-gettext-maps",
"fish-gettext-mo-file-parser",
"fish-printf",
"libc",
"lru",
"macro_rules_attribute",
"nix",
"num-traits",
"once_cell",
"pcre2",
"phf 0.12.1",
"phf_codegen 0.12.1",
"portable-atomic",
"rand",
"rsconf",
@@ -136,43 +121,6 @@ dependencies = [
"widestring",
]
[[package]]
name = "fish-build-helper"
version = "0.0.0"
dependencies = [
"rsconf",
]
[[package]]
name = "fish-build-man-pages"
version = "0.0.0"
dependencies = [
"fish-build-helper",
"rsconf",
]
[[package]]
name = "fish-gettext-extraction"
version = "0.0.0"
dependencies = [
"proc-macro2",
]
[[package]]
name = "fish-gettext-maps"
version = "0.0.0"
dependencies = [
"fish-build-helper",
"fish-gettext-mo-file-parser",
"phf 0.12.1",
"phf_codegen 0.12.1",
"rsconf",
]
[[package]]
name = "fish-gettext-mo-file-parser"
version = "0.0.0"
[[package]]
name = "fish-printf"
version = "0.2.1"
@@ -256,22 +204,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "macro_rules_attribute"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
dependencies = [
"macro_rules_attribute-proc_macro",
"paste",
]
[[package]]
name = "macro_rules_attribute-proc_macro"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "memchr"
version = "2.7.4"
@@ -344,12 +276,6 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pcre2"
version = "0.2.9"
@@ -376,16 +302,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_shared 0.12.1",
"phf_shared",
]
[[package]]
@@ -394,18 +311,8 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
]
[[package]]
name = "phf_codegen"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
dependencies = [
"phf_generator 0.12.1",
"phf_shared 0.12.1",
"phf_generator",
"phf_shared",
]
[[package]]
@@ -414,20 +321,10 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared 0.11.3",
"phf_shared",
"rand",
]
[[package]]
name = "phf_generator"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
dependencies = [
"fastrand",
"phf_shared 0.12.1",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
@@ -437,15 +334,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
"siphasher",
]
[[package]]
name = "pkg-config"
version = "0.3.31"
@@ -460,18 +348,18 @@ checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "proc-macro2"
version = "1.0.95"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@@ -511,9 +399,9 @@ dependencies = [
[[package]]
name = "rust-embed"
version = "8.7.2"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -522,9 +410,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "8.7.2"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
dependencies = [
"proc-macro2",
"quote",
@@ -535,9 +423,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
version = "8.7.2"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
dependencies = [
"sha2",
"walkdir",
@@ -644,8 +532,8 @@ checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
dependencies = [
"fnv",
"nom",
"phf 0.11.3",
"phf_codegen 0.11.3",
"phf",
"phf_codegen",
]
[[package]]

View File

@@ -1,25 +1,41 @@
[workspace]
resolver = "2"
members = ["crates/*"]
members = ["printf"]
[workspace.package]
# To build revisions that use Corrosion (those before 2024-01), use CMake 3.19, Rustc 1.78 and Rustup 1.27.
rust-version = "1.70"
edition = "2021"
repository = "https://github.com/fish-shell/fish-shell"
[workspace.dependencies]
[profile.release]
overflow-checks = true
lto = true
[profile.release-with-debug]
inherits = "release"
debug = true
[package]
name = "fish"
version = "4.1.0-alpha0"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
# 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"
repository = "https://github.com/fish-shell/fish-shell"
homepage = "https://fishshell.com"
readme = "README.rst"
[dependencies]
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
"utf32",
] }
bitflags = "2.5.0"
cc = "1.0.94"
cfg-if = "1.0.3"
errno = "0.3.0"
fish-build-helper = { path = "crates/build-helper" }
fish-build-man-pages = { path = "crates/build-man-pages" }
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-printf = { path = "crates/printf", features = ["widestring"] }
libc = "0.2.155"
libc = "0.2"
# 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.
@@ -32,90 +48,31 @@ nix = { version = "0.30.1", default-features = false, features = [
] }
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",
] }
phf = { version = "0.12", default-features = false }
phf_codegen = { version = "0.12" }
portable-atomic = { version = "1", default-features = false, features = [
"fallback",
] }
proc-macro2 = "1.0"
fish-printf = { path = "./printf", features = ["widestring"] }
# 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"] }
serial_test = { version = "3", default-features = false }
widestring = "1.2.0"
# 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"
[profile.release]
overflow-checks = true
lto = true
[profile.release-with-debug]
inherits = "release"
debug = true
[package]
name = "fish"
version = "4.1.0-snapshot"
edition.workspace = true
rust-version.workspace = true
default-run = "fish"
# 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]
bitflags.workspace = true
cfg-if.workspace = true
errno.workspace = true
fish-build-helper.workspace = true
fish-build-man-pages = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true }
fish-gettext-maps = { workspace = true, optional = true }
fish-printf.workspace = true
libc.workspace = true
lru.workspace = true
macro_rules_attribute = "0.2.2"
nix.workspace = true
num-traits.workspace = true
once_cell.workspace = true
pcre2.workspace = true
phf = { workspace = true, optional = true }
rand.workspace = true
terminfo.workspace = true
widestring.workspace = true
rust-embed = { version = "8.2.0", optional = true }
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic.workspace = true
[target.'cfg(windows)'.dependencies]
rust-embed = { workspace = true, optional = true, features = ["deterministic-timestamps", "debug-embed"] }
[target.'cfg(not(windows))'.dependencies]
rust-embed = { workspace = true, optional = true, features = ["deterministic-timestamps"] }
portable-atomic = { version = "1", default-features = false, features = [
"fallback",
] }
[dev-dependencies]
serial_test.workspace = true
serial_test = { version = "3", default-features = false }
[build-dependencies]
cc.workspace = true
fish-build-helper.workspace = true
fish-gettext-mo-file-parser.workspace = true
phf_codegen = { workspace = true, optional = true }
rsconf.workspace = true
cc = "1.0.94"
rsconf = "0.2.2"
[target.'cfg(windows)'.build-dependencies]
unix_path.workspace = true
unix_path = "1.0.1"
[lib]
crate-type = ["rlib"]
@@ -134,17 +91,9 @@ name = "fish_key_reader"
path = "src/bin/fish_key_reader.rs"
[features]
default = ["embed-data", "localize-messages"]
default = ["embed-data"]
benchmark = []
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 = ["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"]
embed-data = ["dep:rust-embed"]
# The following features are auto-detected by the build-script and should not be enabled manually.
asan = []

View File

@@ -66,8 +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
`Cygwin <https://cygwin.com/>`__ or `MSYS2 <https://github.com/Berrysoft/fish-msys2>`__.
- fish (4.0 on and onwards) cannot be installed in Cygwin, due to a lack of Rust support.
Building from source
~~~~~~~~~~~~~~~~~~~~
@@ -90,14 +89,17 @@ Running fish requires:
- some common \*nix system utilities (currently ``mktemp``), in
addition to the basic POSIX utilities (``cat``, ``cut``, ``dirname``,
``file``, ``ls``, ``mkdir``, ``mkfifo``, ``rm``, ``sh``, ``sort``, ``tee``, ``tr``,
``file``, ``ls``, ``mkdir``, ``mkfifo``, ``rm``, ``sort``, ``tee``, ``tr``,
``uname`` and ``sed`` at least, but the full coreutils plus ``find`` and
``awk`` is preferred)
- The gettext library, if compiled with
translation support
The following optional features also have specific requirements:
- builtin commands that have the ``--help`` option or print usage
messages require ``man`` for display
messages require ``nroff`` or ``mandoc`` for
display
- automated completion generation from manual pages requires Python 3.5+
- the ``fish_config`` web configuration tool requires Python 3.5+ and a web browser
- system clipboard integration (with the default Ctrl-V and Ctrl-X
@@ -122,13 +124,13 @@ Compiling fish requires:
- 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
- gettext (only the msgfmt tool) - optional, for translation support
- gettext (headers and libraries) - optional, for translation support
- an Internet connection, as other dependencies will be downloaded automatically
Sphinx is also optionally required to build the documentation from a
cloned git repository.
Additionally, running the full test suite requires diff, git, Python 3.5+, pexpect, less, tmux and wget.
Additionally, running the full test suite requires Python 3, tmux, and the pexpect package.
Building from source with CMake
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -162,7 +164,7 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
- 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.
- 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.
- WITH_GETTEXT=ON|OFF - whether to build with gettext support for 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 embedded data (experimental)
@@ -177,20 +179,19 @@ Fish will then read these right from its own binary, and print them out when nee
To install fish with embedded files, just use ``cargo``, like::
cargo install --path /path/to/fish # if you have a git clone
cargo install --git https://github.com/fish-shell/fish-shell --tag "$(curl -s https://api.github.com/repos/fish-shell/fish-shell/releases/latest | jq -r .tag_name)" # to build the latest release
cargo install --git https://github.com/fish-shell/fish-shell # to build the latest development snapshot
cargo install --git https://github.com/fish-shell/fish-shell --tag 4.0.0 # to build from git with a specific version
cargo install --git https://github.com/fish-shell/fish-shell # to build the current development snapshot without cloning
This will place the standalone binaries in ``~/.cargo/bin/``, but you can place them wherever you want.
This will place the binaries in ``~/.cargo/bin/``, but you can place them wherever you want.
This build won't have the HTML docs (``help`` will open the online version) or translations.
This build won't have the HTML docs (``help`` will open the online version).
It will try to build the man pages with sphinx-build. If that is not available and you would like to include man pages, you need to install it and retrigger the build script, e.g. by setting FISH_BUILD_DOCS=1::
FISH_BUILD_DOCS=1 cargo install --path .
Setting it to "0" disables the inclusion of man pages.
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.
Contributing Changes to the Code

339
build.rs
View File

@@ -1,13 +1,9 @@
#![allow(clippy::uninlined_format_args)]
use fish_build_helper::{env_var, fish_build_dir, workspace_root};
use rsconf::Target;
use rsconf::{LinkType, Target};
use std::env;
use std::path::{Path, PathBuf};
fn canonicalize<P: AsRef<Path>>(path: P) -> PathBuf {
std::fs::canonicalize(path).unwrap()
}
use std::error::Error;
use std::path::Path;
fn main() {
setup_paths();
@@ -15,24 +11,28 @@ fn main() {
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
// language server.
rsconf::set_env_value(
"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.
canonicalize(fish_build_dir()).to_str().unwrap(),
);
// FISH_BUILD_DIR is set by CMake, if we are using it.
// OUT_DIR is set by Cargo when the build script is running (not compiling)
let default_build_dir = env::var("OUT_DIR").unwrap();
let build_dir = option_env!("FISH_BUILD_DIR").unwrap_or(&default_build_dir);
let build_dir = std::fs::canonicalize(build_dir).unwrap();
let build_dir = build_dir.to_str().unwrap();
rsconf::set_env_value("FISH_BUILD_DIR", build_dir);
// 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",
canonicalize(workspace_root()).to_str().unwrap(),
std::fs::canonicalize(env!("CARGO_MANIFEST_DIR"))
.unwrap()
.as_path()
.to_str()
.unwrap(),
);
// Some build info
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env_var("TARGET").unwrap());
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap());
rsconf::set_env_value("BUILD_TARGET_TRIPLE", &env::var("TARGET").unwrap());
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env::var("HOST").unwrap());
rsconf::set_env_value("BUILD_PROFILE", &env::var("PROFILE").unwrap());
let version = &get_version(&env::current_dir().unwrap());
// Per https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script,
@@ -41,16 +41,37 @@ fn main() {
std::env::set_var("FISH_BUILD_VERSION", version);
let cman = std::fs::canonicalize(env!("CARGO_MANIFEST_DIR")).unwrap();
let targetman = cman.as_path().join("target").join("man");
#[cfg(feature = "embed-data")]
#[cfg(not(clippy))]
{
build_man(&targetman);
}
#[cfg(any(not(feature = "embed-data"), clippy))]
{
let sec1dir = targetman.join("man1");
let _ = std::fs::create_dir_all(sec1dir.to_str().unwrap());
}
rsconf::rebuild_if_paths_changed(&["src", "printf", "Cargo.toml", "Cargo.lock", "build.rs"]);
// 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(not(debug_assertions))]
rsconf::rebuild_if_paths_changed(&["doc_src", "share"]);
#[cfg(feature = "gettext-extract")]
rsconf::rebuild_if_env_changed("FISH_GETTEXT_EXTRACTION_FILE");
cc::Build::new()
.file("src/libc.c")
.include(build_dir)
.compile("flibc.a");
let build = cc::Build::new();
let mut build = cc::Build::new();
// Add to the default library search path
build.flag_if_supported("-L/usr/local/lib/");
rsconf::add_library_search_path("/usr/local/lib");
let mut target = Target::new_from(build).unwrap();
// Keep verbose mode on until we've ironed out rust build script stuff
target.set_verbose(true);
@@ -77,44 +98,54 @@ fn detect_cfgs(target: &mut Target) {
for (name, handler) in [
// 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),
(
"",
&(|_: &Target| Ok(false)) as &dyn Fn(&Target) -> Result<bool, Box<dyn Error>>,
),
("apple", &detect_apple),
("bsd", &detect_bsd),
("cygwin", &detect_cygwin),
("gettext", &have_gettext),
("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")
Ok(target.has_symbol("localeconv_l"))
}),
("FISH_USE_POSIX_SPAWN", &|target| {
target.has_header("spawn.h")
Ok(target.has_header("spawn.h"))
}),
("HAVE_PIPE2", &|target| {
target.has_symbol("pipe2")
Ok(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
Ok(false)
} else {
target.has_header("sys/eventfd.h")
Ok(target.has_header("sys/eventfd.h"))
}
}),
("HAVE_WAITSTATUS_SIGNAL_RET", &|target| {
target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"])
Ok(target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"]))
}),
] {
rsconf::declare_cfg(name, handler(target))
match handler(target) {
Err(e) => {
rsconf::warn!("{}: {}", name, e);
rsconf::declare_cfg(name, false);
},
Ok(enabled) => rsconf::declare_cfg(name, enabled),
}
}
}
fn detect_apple(_: &Target) -> bool {
cfg!(any(target_os = "ios", target_os = "macos"))
fn detect_apple(_: &Target) -> Result<bool, Box<dyn Error>> {
Ok(cfg!(any(target_os = "ios", target_os = "macos")))
}
fn detect_cygwin(_: &Target) -> bool {
fn detect_cygwin(_: &Target) -> Result<bool, Box<dyn Error>> {
// Cygwin target is usually cross-compiled.
env_var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin"
Ok(std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "cygwin")
}
/// Detect if we're being compiled for a BSD-derived OS, allowing targeting code conditionally with
@@ -123,14 +154,13 @@ fn detect_cygwin(_: &Target) -> bool {
/// 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 {
fn detect_bsd(_: &Target) -> Result<bool, Box<dyn Error>> {
// 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();
let mut target = std::env::var("TARGET").unwrap();
if !target.chars().all(|c| c.is_ascii_lowercase()) {
target = target.to_ascii_lowercase();
}
#[allow(clippy::let_and_return)] // for old clippy
let is_bsd = target.ends_with("bsd") || target.ends_with("dragonfly");
#[cfg(any(
target_os = "dragonfly",
@@ -139,7 +169,52 @@ fn detect_bsd(_: &Target) -> bool {
target_os = "openbsd",
))]
assert!(is_bsd, "Target incorrectly detected as not BSD!");
is_bsd
Ok(is_bsd)
}
/// Detect libintl/gettext and its needed symbols to enable internationalization/localization
/// support.
fn have_gettext(target: &Target) -> Result<bool, Box<dyn Error>> {
// The following script correctly detects and links against gettext, but so long as we are using
// C++ and generate a static library linked into the C++ binary via CMake, we need to account
// for the CMake option WITH_GETTEXT being explicitly disabled.
rsconf::rebuild_if_env_changed("CMAKE_WITH_GETTEXT");
if let Some(with_gettext) = std::env::var_os("CMAKE_WITH_GETTEXT") {
if with_gettext.eq_ignore_ascii_case("0") {
return Ok(false);
}
}
// In order for fish to correctly operate, we need some way of notifying libintl to invalidate
// its localizations when the locale environment variables are modified. Without the libintl
// symbol _nl_msg_cat_cntr, we cannot use gettext even if we find it.
let mut libraries = Vec::new();
let mut found = 0;
let symbols = ["gettext", "_nl_msg_cat_cntr"];
for symbol in &symbols {
// Historically, libintl was required in order to use gettext() and co, but that
// functionality was subsumed by some versions of libc.
if target.has_symbol(symbol) {
// No need to link anything special for this symbol
found += 1;
continue;
}
for library in ["intl", "gettextlib"] {
if target.has_symbol_in(symbol, &[library]) {
libraries.push(library);
found += 1;
continue;
}
}
}
match found {
0 => Ok(false),
1 => Err(format!("gettext found but cannot be used without {}", symbols[1]).into()),
_ => {
rsconf::link_libraries(&libraries, LinkType::Default);
Ok(true)
}
}
}
/// Rust sets the stack size of newly created threads to a sane value, but is at at the mercy of the
@@ -148,13 +223,13 @@ fn detect_bsd(_: &Target) -> bool {
///
/// 0.5 MiB is small enough that we'd have to drastically reduce MAX_STACK_DEPTH to less than 10, so
/// we instead use a workaround to increase the main thread size.
fn has_small_stack(_: &Target) -> bool {
fn has_small_stack(_: &Target) -> Result<bool, Box<dyn Error>> {
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "netbsd")))]
return false;
return Ok(false);
// NetBSD 10 also needs this but can't find pthread_get_stacksize_np.
#[cfg(target_os = "netbsd")]
return true;
return Ok(true);
#[cfg(any(target_os = "ios", target_os = "macos"))]
{
@@ -169,79 +244,87 @@ fn has_small_stack(_: &Target) -> bool {
// Modern macOS versions default to an 8 MiB main stack but legacy OS X have a 0.5 MiB one.
let stack_size = unsafe { pthread_get_stacksize_np(pthread_self()) };
const TWO_MIB: usize = 2 * 1024 * 1024 - 1;
stack_size <= TWO_MIB
match stack_size {
0..=TWO_MIB => Ok(true),
_ => Ok(false),
}
}
}
fn setup_paths() {
#[cfg(unix)]
use std::path::PathBuf;
#[cfg(windows)]
use unix_path::{Path, PathBuf};
fn overridable_path(env_var_name: &str, f: impl FnOnce(Option<String>) -> PathBuf) -> PathBuf {
rsconf::rebuild_if_env_changed(env_var_name);
let path = f(env_var(env_var_name));
rsconf::set_env_value(env_var_name, path.to_str().unwrap());
path
}
fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
let path = PathBuf::from(path);
if path.is_relative() {
parent_if_relative.join(path)
} else {
path
fn get_path(name: &str, default: &str, onvar: &Path) -> PathBuf {
let mut var = PathBuf::from(env::var(name).unwrap_or(default.to_string()));
if var.is_relative() {
var = onvar.join(var);
}
var
}
let prefix = overridable_path("PREFIX", |env_prefix| {
PathBuf::from(env_prefix.unwrap_or("/usr/local".to_string()))
});
let (prefix_from_home, prefix) = if let Ok(pre) = env::var("PREFIX") {
(false, PathBuf::from(pre))
} else {
(true, PathBuf::from(".local/"))
};
let datadir = join_if_relative(&prefix, env_var("DATADIR").unwrap_or("share/".to_string()));
rsconf::rebuild_if_env_changed("DATADIR");
#[cfg(not(feature = "embed-data"))]
// If someone gives us a $PREFIX, we need it to be absolute.
// Otherwise we would try to get it from $HOME and that won't really work.
if !prefix_from_home && prefix.is_relative() {
panic!("Can't have relative prefix");
}
rsconf::rebuild_if_env_changed("PREFIX");
rsconf::set_env_value("PREFIX", prefix.to_str().unwrap());
let datadir = get_path("DATADIR", "share/", &prefix);
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DATADIR");
overridable_path("SYSCONFDIR", |env_sysconfdir| {
join_if_relative(
&datadir,
env_sysconfdir.unwrap_or(
// Embedded builds use "/etc," not "./share/etc".
if cfg!(feature = "embed-data") {
"/etc/"
} else {
"etc/"
}
.to_string(),
),
)
});
let datadir_subdir = if prefix_from_home {
"fish/install"
} else {
"fish"
};
rsconf::set_env_value("DATADIR_SUBDIR", datadir_subdir);
#[cfg(not(feature = "embed-data"))]
{
overridable_path("BINDIR", |env_bindir| {
join_if_relative(&prefix, env_bindir.unwrap_or("bin/".to_string()))
});
overridable_path("LOCALEDIR", |env_localedir| {
join_if_relative(&datadir, env_localedir.unwrap_or("locale/".to_string()))
});
overridable_path("DOCDIR", |env_docdir| {
join_if_relative(&datadir, env_docdir.unwrap_or("doc/fish".to_string()))
});
}
let bindir = get_path("BINDIR", "bin/", &prefix);
rsconf::set_env_value("BINDIR", bindir.to_str().unwrap());
rsconf::rebuild_if_env_changed("BINDIR");
let sysconfdir = get_path(
"SYSCONFDIR",
// If we get our prefix from $HOME, we should use the system's /etc/
// ~/.local/share/etc/ makes no sense
if prefix_from_home { "/etc/" } else { "etc/" },
&datadir,
);
rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("SYSCONFDIR");
let localedir = get_path("LOCALEDIR", "locale/", &datadir);
rsconf::set_env_value("LOCALEDIR", localedir.to_str().unwrap());
rsconf::rebuild_if_env_changed("LOCALEDIR");
let docdir = get_path("DOCDIR", "doc/fish", &datadir);
rsconf::set_env_value("DOCDIR", docdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DOCDIR");
}
fn get_version(src_dir: &Path) -> String {
use std::fs::read_to_string;
use std::process::Command;
if let Some(var) = env_var("FISH_BUILD_VERSION") {
if let Ok(var) = std::env::var("FISH_BUILD_VERSION") {
return var;
}
let path = src_dir.join("version");
if let Ok(strver) = read_to_string(path) {
return strver;
return strver.to_string();
}
let args = &["describe", "--always", "--dirty=-dirty"];
@@ -268,9 +351,8 @@ fn get_version(src_dir: &Path) -> String {
// 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 gitdir = Path::new(env!("CARGO_MANIFEST_DIR")).join(".git");
let jjdir = Path::new(env!("CARGO_MANIFEST_DIR")).join(".jj");
let commit_id = if gitdir.exists() {
// .git/HEAD contains ref: refs/heads/branch
let headpath = gitdir.join("HEAD");
@@ -307,3 +389,74 @@ fn get_git_hash() -> Result<String, Box<dyn std::error::Error>> {
get_git_hash().expect("Could not get a version. Either set $FISH_BUILD_VERSION or install git.")
}
#[cfg(feature = "embed-data")]
// disable clippy because otherwise it would panic without sphinx
#[cfg(not(clippy))]
fn build_man(build_dir: &Path) {
use std::process::Command;
let mandir = build_dir;
let sec1dir = mandir.join("man1");
let docsrc_path = std::fs::canonicalize(env!("CARGO_MANIFEST_DIR"))
.unwrap()
.as_path()
.join("doc_src");
let docsrc = docsrc_path.to_str().unwrap();
let args = &[
"-j",
"auto",
"-q",
"-b",
"man",
"-c",
docsrc,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d",
mandir.to_str().unwrap(),
docsrc,
sec1dir.to_str().unwrap(),
];
let _ = std::fs::create_dir_all(sec1dir.to_str().unwrap());
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env::var("FISH_BUILD_DOCS") == Ok("0".to_string()) {
println!("cargo:warning=Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
// We run sphinx to build the man pages.
// Every error here is fatal so cargo doesn't cache the result
// - if we skipped the docs with sphinx not installed, installing it would not then build the docs.
// That means you need to explicitly set $FISH_BUILD_DOCS=0 (`FISH_BUILD_DOCS=0 cargo install --path .`),
// which is unfortunate - but the docs are pretty important because they're also used for --help.
match Command::new("sphinx-build").args(args).spawn() {
Err(x) if x.kind() == std::io::ErrorKind::NotFound => {
if env::var("FISH_BUILD_DOCS") == Ok("1".to_string()) {
panic!("Could not find sphinx-build to build man pages.\nInstall sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0.");
}
println!("cargo:warning=Cannot find sphinx-build to build man pages.");
println!("cargo:warning=If you install it now you need to run `cargo clean` and rebuild, or set $FISH_BUILD_DOCS=1 explicitly.");
}
Err(x) => {
// Another error - permissions wrong etc
panic!("Error starting sphinx-build to build man pages: {:?}", x);
}
Ok(mut x) => match x.wait() {
Err(err) => {
panic!(
"Error waiting for sphinx-build to build man pages: {:?}",
err
);
}
Ok(out) => {
if out.success() {
// Success!
return;
} else {
panic!("sphinx-build failed to build the man pages.");
}
}
},
}
}

View File

@@ -1,70 +0,0 @@
#!/bin/sh
{
set -ex
lint=true
if [ "$FISH_CHECK_LINT" = false ]; then
lint=false
fi
cargo_args=$FISH_CHECK_CARGO_ARGS
target_triple=$FISH_CHECK_TARGET_TRIPLE
if [ -n "$target_triple" ]; then
cargo_args="$cargo_args --target=$FISH_CHECK_TARGET_TRIPLE"
fi
cargo() {
subcmd=$1
shift
# shellcheck disable=2086
command cargo "$subcmd" $cargo_args "$@"
}
cleanup () {
if [ -n "$template_file" ] && [ -e "$template_file" ]; then
rm "$template_file"
fi
}
trap cleanup EXIT INT TERM HUP
if $lint; then
export RUSTFLAGS="--deny=warnings ${RUSTFLAGS}"
export RUSTDOCFLAGS="--deny=warnings ${RUSTDOCFLAGS}"
fi
workspace_root="$(dirname "$0")/.."
target_dir=${CARGO_TARGET_DIR:-$workspace_root/target}
if [ -n "$target_triple" ]; then
target_dir="$target_dir/$target_triple"
fi
# The directory containing the binaries produced by cargo/rustc.
# Currently, all builds are debug builds.
build_dir="$target_dir/debug"
if [ -n "$FISH_TEST_MAX_CONCURRENCY" ]; then
export RUST_TEST_THREADS="$FISH_TEST_MAX_CONCURRENCY"
export CARGO_BUILD_JOBS="$FISH_TEST_MAX_CONCURRENCY"
fi
template_file=$(mktemp)
(
export FISH_GETTEXT_EXTRACTION_FILE="$template_file"
cargo build --workspace --all-targets --features=gettext-extract
)
if $lint; then
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
fi
cargo test --no-default-features --workspace --all-targets
cargo test --doc --workspace
if $lint; then
cargo doc --workspace
fi
FISH_GETTEXT_EXTRACTION_FILE=$template_file "$workspace_root/tests/test_driver.py" "$build_dir"
exit
}

View File

@@ -5,8 +5,7 @@
#
# Usage: ./diff_profiles.fish profile1.log profile2.log > profile_diff.log
if test (count $argv) -ne 2
if test (count $argv) -ne 2;
echo "Incorrect number of arguments."
echo "Usage: "(status filename)" profile1.log profile2.log"
exit 1

View File

@@ -2,10 +2,6 @@
#
# 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.
@@ -19,38 +15,48 @@ begin
echo ""
end
set -g workspace_root (path resolve (status dirname)/..)
set -l cargo_expanded_file (mktemp)
# This is a gigantic crime.
# We use cargo-expand to get all our wgettext invocations.
# This might be replaced once we have a tool which properly handles macro expansions.
begin
cargo expand --lib
for f in fish fish_indent fish_key_reader
cargo expand --bin $f
end
end >$cargo_expanded_file
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
set -l rust_string_file (mktemp)
function mark_section
set -l section_name $argv[1]
echo 'msgid "fish-section-'$section_name'"'
echo 'msgstr ""'
echo ''
end
# Extract any gettext call
grep -A1 wgettext_static_str <$cargo_expanded_file |
grep 'widestring::internals::core::primitive::str =' |
string match -rg '"(.*)"' |
string match -rv '^%ls$|^$' |
# escaping difference between gettext and cargo-expand: single-quotes
string replace -a "\'" "'" >$rust_string_file
mark_section tier1-from-rust
# Extract any constants
grep -Ev 'BUILD_VERSION:|PACKAGE_NAME' <$cargo_expanded_file |
grep -E 'const [A-Z_]*: &str = "(.*)"' |
sed -E -e 's/^.*const [A-Z_]*: &str = "(.*)".*$/\1/' -e "s_\\\'_'_g" >>$rust_string_file
# Get rid of duplicates and sort.
msguniq --no-wrap --sort-output $rust_extraction_file
or exit 1
rm $cargo_expanded_file
if not set -l --query _flag_use_existing_template
rm $rust_extraction_file
end
# Sort the extracted strings and remove duplicates.
# Then, transform them into the po format.
# If a string contains a '%' it is considered a format string and marked with a '#, c-format'.
# This allows msgfmt to identify issues with translations whose format string does not match the
# original.
sort -u $rust_string_file |
sed -E -e '/%/ i\
#, c-format
' -e 's/^(.*)$/msgid "\1"\nmsgstr ""\n/'
rm $rust_string_file
function extract_fish_script_messages --argument-names regex
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).
@@ -69,74 +75,22 @@ begin
# 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 |
cat share/config.fish share/completions/*.fish share/functions/*.fish |
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 explicit requests to translate a message. These are more important to translate
# than messages which should be implicitly translated.
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
extract_fish_script_messages $explicit_regex
# 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
# 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).*'
extract_fish_script_messages $implicit_regex
end |
# At this point, all extracted strings have been written to stdout,
# starting with the ones taken from the Rust sources,

View File

@@ -1,177 +0,0 @@
#!/usr/bin/env bash
# Script to produce an OS X installer .pkg and .app(.zip)
usage() {
echo "Build macOS packages, optionally signing and notarizing them."
echo "Usage: $0 options"
echo "Options:"
echo " -s Enables code signing"
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
echo " -n Enables notarization. This will fail if code signing is not also enabled."
echo " -j <API_KEY.JSON> Path to JSON file generated with \`rcodesign encode-app-store-connect-api-key\` (required for notarization)"
echo
exit 1
}
set -x
set -e
SIGN=
NOTARIZE=
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.9'
# As of this writing, the most recent Rust release supports macOS back to 10.12.
# The first supported version of macOS on arm64 is 10.15, so any Rust is fine for arm64.
# We wish to support back to 10.9 on x86-64; the last version of Rust to support that is
# version 1.73.0.
RUST_VERSION_X86_64=1.70.0
while getopts "sf:i:p:e:nj:" opt; do
case $opt in
s) SIGN=1;;
f) P12_APP_FILE=$(realpath "$OPTARG");;
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
p) P12_PASSWORD="$OPTARG";;
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
n) NOTARIZE=1;;
j) API_KEY_FILE=$(realpath "$OPTARG");;
\?) usage;;
esac
done
if [ -n "$SIGN" ] && { [ -z "$P12_APP_FILE" ] || [ -z "$P12_INSTALL_FILE" ] || [ -z "$P12_PASSWORD" ]; }; then
usage
fi
if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
usage
fi
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
echo "Version is $VERSION"
PKGDIR=$(mktemp -d)
echo "$PKGDIR"
SRC_DIR=$PWD
OUTPUT_PATH=${FISH_ARTEFACT_PATH:-~/fish_built}
mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/intermediates" "$PKGDIR/dst"
# Build and install for arm64.
# Pass FISH_USE_SYSTEM_PCRE2=OFF because a system PCRE2 on macOS will not be signed by fish,
# and will probably not be built universal, so the package will fail to validate/run on other systems.
# Note CMAKE_OSX_ARCHITECTURES is still relevant for the Mac app.
{ cd "$PKGDIR/build_arm64" \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DRust_CARGO_TARGET=aarch64-apple-darwin \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF \
"$SRC_DIR" \
&& env $ARM64_DEPLOY_TARGET make VERBOSE=1 -j 12 \
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
}
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
# Set RUST_VERSION_X86_64 to the last version of Rust that supports macOS 10.9.
{ cd "$PKGDIR/build_x86_64" \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DRust_TOOLCHAIN="$RUST_VERSION_X86_64" \
-DRust_CARGO_TARGET=x86_64-apple-darwin \
-DRust_COMPILER="$(rustup +$RUST_VERSION_X86_64 which rustc)" \
-DRust_CARGO="$(rustup +$RUST_VERSION_X86_64 which cargo)" \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF "$SRC_DIR" \
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
# 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"
ARGS=(
--p12-file "$P12_APP_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
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"
productbuild --package-path "$PKGDIR/intermediates" --distribution "$SRC_DIR/build_tools/osx_distribution.xml" --resources "$SRC_DIR/build_tools/osx_package_resources/" "$OUTPUT_PATH/fish-$VERSION.pkg"
if test -n "$SIGN"; then
echo "Signing installer"
ARGS=(
--p12-file "$P12_INSTALL_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
(set +x; rcodesign sign "${ARGS[@]}" "$OUTPUT_PATH/fish-$VERSION.pkg")
fi
# Make the app
(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 binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
cd "$PKGDIR/build_arm64"
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"
ARGS=(
--p12-file "$P12_APP_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
(set +x; rcodesign sign "${ARGS[@]}" "fish.app")
fi
cp -R "fish.app" "$OUTPUT_PATH/fish-$VERSION.app"
cd "$OUTPUT_PATH"
# Maybe notarize.
if test -n "$NOTARIZE"; then
echo "Notarizing"
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.pkg"
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.app"
fi
# Zip it up.
zip -r "fish-$VERSION.app.zip" "fish-$VERSION.app" && rm -Rf "fish-$VERSION.app"
rm -rf "$PKGDIR"

View File

@@ -1 +0,0 @@
make_macos_pkg.sh

183
build_tools/make_pkg.sh Executable file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env bash
# Script to produce an OS X installer .pkg and .app(.zip)
usage() {
echo "Build macOS packages, optionally signing and notarizing them."
echo "Usage: $0 options"
echo "Options:"
echo " -s Enables code signing"
echo " -f <APP_KEY.p12> Path to .p12 file for application signing"
echo " -i <INSTALLER_KEY.p12> Path to .p12 file for installer signing"
echo " -p <PASSWORD> Password for the .p12 files (necessary to access the certificates)"
echo " -e <entitlements file> (Optional) Path to an entitlements XML file"
echo " -n Enables notarization. This will fail if code signing is not also enabled."
echo " -j <API_KEY.JSON> Path to JSON file generated with \`rcodesign encode-app-store-connect-api-key\` (required for notarization)"
echo
exit 1
}
set -x
set -e
SIGN=
NOTARIZE=
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.9'
# As of this writing, the most recent Rust release supports macOS back to 10.12.
# The first supported version of macOS on arm64 is 10.15, so any Rust is fine for arm64.
# We wish to support back to 10.9 on x86-64; the last version of Rust to support that is
# version 1.73.0.
RUST_VERSION_X86_64=1.73.0
while getopts "sf:i:p:e:nj:" opt; do
case $opt in
s) SIGN=1;;
f) P12_APP_FILE=$(realpath "$OPTARG");;
i) P12_INSTALL_FILE=$(realpath "$OPTARG");;
p) P12_PASSWORD="$OPTARG";;
e) ENTITLEMENTS_FILE=$(realpath "$OPTARG");;
n) NOTARIZE=1;;
j) API_KEY_FILE=$(realpath "$OPTARG");;
\?) usage;;
esac
done
if [ -n "$SIGN" ] && { [ -z "$P12_APP_FILE" ] || [ -z "$P12_INSTALL_FILE" ] || [ -z "$P12_PASSWORD" ]; }; then
usage
fi
if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
usage
fi
VERSION=$(git describe --always --dirty 2>/dev/null)
if test -z "$VERSION" ; then
echo "Could not get version from git"
if test -f version; then
VERSION=$(cat version)
fi
fi
echo "Version is $VERSION"
PKGDIR=$(mktemp -d)
echo "$PKGDIR"
SRC_DIR=$PWD
OUTPUT_PATH=${FISH_ARTEFACT_PATH:-~/fish_built}
mkdir -p "$PKGDIR/build_x86_64" "$PKGDIR/build_arm64" "$PKGDIR/root" "$PKGDIR/intermediates" "$PKGDIR/dst"
# Build and install for arm64.
# Pass FISH_USE_SYSTEM_PCRE2=OFF because a system PCRE2 on macOS will not be signed by fish,
# and will probably not be built universal, so the package will fail to validate/run on other systems.
# Note CMAKE_OSX_ARCHITECTURES is still relevant for the Mac app.
{ cd "$PKGDIR/build_arm64" \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DWITH_GETTEXT=OFF \
-DRust_CARGO_TARGET=aarch64-apple-darwin \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF \
"$SRC_DIR" \
&& env $ARM64_DEPLOY_TARGET make VERBOSE=1 -j 12 \
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
}
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
# Set RUST_VERSION_X86_64 to the last version of Rust that supports macOS 10.9.
{ cd "$PKGDIR/build_x86_64" \
&& cmake \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,-ld_classic" \
-DWITH_GETTEXT=OFF \
-DRust_TOOLCHAIN="$RUST_VERSION_X86_64" \
-DRust_CARGO_TARGET=x86_64-apple-darwin \
-DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \
-DFISH_USE_SYSTEM_PCRE2=OFF "$SRC_DIR" \
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
# 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"
ARGS=(
--p12-file "$P12_APP_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
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"
productbuild --package-path "$PKGDIR/intermediates" --distribution "$SRC_DIR/build_tools/osx_distribution.xml" --resources "$SRC_DIR/build_tools/osx_package_resources/" "$OUTPUT_PATH/fish-$VERSION.pkg"
if test -n "$SIGN"; then
echo "Signing installer"
ARGS=(
--p12-file "$P12_INSTALL_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
(set +x; rcodesign sign "${ARGS[@]}" "$OUTPUT_PATH/fish-$VERSION.pkg")
fi
# Make the app
(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 binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
cd "$PKGDIR/build_arm64"
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"
ARGS=(
--p12-file "$P12_APP_FILE"
--p12-password "$P12_PASSWORD"
--code-signature-flags runtime
--for-notarization
)
if [ -n "$ENTITLEMENTS_FILE" ]; then
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
fi
(set +x; rcodesign sign "${ARGS[@]}" "fish.app")
fi
cp -R "fish.app" "$OUTPUT_PATH/fish-$VERSION.app"
cd "$OUTPUT_PATH"
# Maybe notarize.
if test -n "$NOTARIZE"; then
echo "Notarizing"
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.pkg"
rcodesign notarize --staple --wait --max-wait-seconds 1800 --api-key-file "$API_KEY_FILE" "$OUTPUT_PATH/fish-$VERSION.app"
fi
# Zip it up.
zip -r "fish-$VERSION.app.zip" "fish-$VERSION.app" && rm -Rf "fish-$VERSION.app"
rm -rf "$PKGDIR"

View File

@@ -9,38 +9,38 @@
# Exit on error
set -e
# We will generate a tarball with a prefix "fish-VERSION"
# We wil 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
# Use Ninja if available, as it automatically paralellises
BUILD_TOOL="make"
BUILD_GENERATOR="Unix Makefiles"
if command -v ninja >/dev/null; then
BUILD_TOOL="ninja"
BUILD_GENERATOR="Ninja"
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
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
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 --stdout 2>/dev/null)
# Get the version from git-describe
VERSION=$(git describe --dirty 2>/dev/null)
# The name of the prefix, which is the directory that you get when you untar
prefix="fish-$VERSION"
@@ -63,7 +63,7 @@ cmake -G "$BUILD_GENERATOR" -DCMAKE_BUILD_TYPE=Debug "$wd"
$BUILD_TOOL doc
TAR_APPEND="$TAR --append --file=$path --mtime=now --owner=0 --group=0 \
--mode=g+w,a+rX --transform s/^/$prefix\//"
--mode=g+w,a+rX --transform s/^/$prefix\//"
$TAR_APPEND --no-recursion user_doc
$TAR_APPEND user_doc/html user_doc/man
$TAR_APPEND version

View File

@@ -11,22 +11,22 @@ 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
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
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 --stdout 2>/dev/null)
VERSION=$(git describe --dirty 2>/dev/null)
# The name of the prefix, which is the directory that you get when you untar
prefix="fish-$VERSION"

View File

@@ -1,104 +0,0 @@
#!/bin/sh
set -e
workspace_root=$(dirname "$0")/..
relnotes_tmp=$(mktemp -d)
mkdir -p "$relnotes_tmp/fake-workspace" "$relnotes_tmp/out"
(
cd "$workspace_root"
cp -r doc_src CONTRIBUTING.rst README.rst "$relnotes_tmp/fake-workspace"
)
version=$(sed 's,^fish \(\S*\) .*,\1,; 1q' "$workspace_root/CHANGELOG.rst")
previous_version=$(
cd "$workspace_root"
awk <CHANGELOG.rst '
( /^fish \S*\.\S*\.\S* \(released .*\)$/ &&
NR > 1 &&
# Skip tags that have not been created yet..
system("git rev-parse --verify >/dev/null --quiet refs/tags/"$2) == 0 \
) {
print $2; ok = 1; exit
}
END { exit !ok }
'
)
minor_version=${version%.*}
previous_minor_version=${previous_version%.*}
{
sed -n 1,2p <"$workspace_root/CHANGELOG.rst"
ListCommitters() {
comm "$@" "$relnotes_tmp/committers-then" "$relnotes_tmp/committers-now"
}
(
cd "$workspace_root"
git log "$previous_version" --format="%aN" | sort -u >"$relnotes_tmp/committers-then"
git log "$previous_version".. --format="%aN" | sort -u >"$relnotes_tmp/committers-now"
ListCommitters -13 >"$relnotes_tmp/committers-new"
ListCommitters -12 >"$relnotes_tmp/committers-returning"
)
if [ "$minor_version" != "$previous_minor_version" ]; then
(
cd "$workspace_root"
num_commits=$(git log --no-merges --format=%H "$previous_version".. | wc -l)
num_authors=$(wc -l <"$relnotes_tmp/committers-now")
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
printf %s \
"This release comprises $num_commits commits since $previous_version," \
" contributed by $num_authors authors, $num_new_authors of which are new committers."
echo
echo
)
fi
printf '%s\n' "$(awk <"$workspace_root/CHANGELOG.rst" '
NR <= 2 || /^\.\. ignore / { next }
/^===/ { exit }
{ print }
' | sed '$d')" |
sed -e '$s/^----*$//' # Remove spurious transitions at the end of the document.
if [ "$minor_version" != "$previous_minor_version" ]; then {
JoinEscaped() {
sed 's/\S/\\&/g' |
awk '
NR != 1 { printf ",\n" }
{ printf "%s", $0 }
END { printf "\n" }
'
}
echo ""
echo "---"
echo ""
echo "Thanks to everyone who contributed through issue discussions, code reviews, or code changes."
echo
printf "Welcome our new committers: "
JoinEscaped <"$relnotes_tmp/committers-new"
echo
printf "Welcome back our returning committers: "
JoinEscaped <"$relnotes_tmp/committers-returning"
} fi
echo
echo "---"
echo
echo "*Download links: To download the source code for fish, we suggest the file named \"fish-$version.tar.xz\". The file downloaded from \"Source code (tar.gz)\" will not build correctly.*"
echo
echo "*The files called fish-$version-linux-\*.tar.xz are experimental packages containing a single standalone ``fish`` binary for any Linux with the given CPU architecture.*"
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst
sphinx-build >&2 -j auto \
-W -E -b markdown -c "$workspace_root/doc_src" \
-d "$relnotes_tmp/doctree" "$relnotes_tmp/fake-workspace/doc_src" "$relnotes_tmp/out" \
-D markdown_http_base="https://fishshell.com/docs/$minor_version" \
-D markdown_uri_doc_suffix=".html" \
-D markdown_github_flavored=1 \
"$@"
# Skip changelog header
sed -n 1p "$relnotes_tmp/out/relnotes.md" | grep -Fxq "# Release notes"
sed -n 2p "$relnotes_tmp/out/relnotes.md" | grep -Fxq ''
sed 1,2d "$relnotes_tmp/out/relnotes.md"
rm -r "$relnotes_tmp"

View File

@@ -1,253 +0,0 @@
#!/bin/sh
{
set -ex
version=$1
repository_owner=fish-shell
remote=origin
if [ -n "$2" ]; then
set -u
repository_owner=$2
remote=$3
set +u
[ $# -eq 3 ]
fi
[ -n "$version" ]
for tool in \
bundle \
gh \
jq \
ruby \
timeout \
; do
if ! command -v "$tool" >/dev/null; then
echo >&2 "$0: missing command: $1"
exit 1
fi
done
repo_root="$(dirname "$0")/.."
fish_site=$repo_root/../fish-site
fish_site_repo=git@github.com:$repository_owner/fish-site
for path in . "$fish_site"
do
if ! git -C "$path" diff HEAD --quiet ||
git ls-files --others --exclude-standard | grep .; then
echo >&2 "$0: index and worktree must be clean"
exit 1
fi
done
(
cd "$fish_site"
[ "$(git rev-parse HEAD)" = \
"$(git ls-remote "$fish_site_repo" refs/heads/master |
awk '{print $1}')" ]
)
if git tag | grep -qxF "$version"; then
echo >&2 "$0: tag $version already exists"
exit 1
fi
integration_branch=$(
git for-each-ref --points-at=HEAD 'refs/heads/Integration_*' \
--format='%(refname:strip=2)'
)
[ -n "$integration_branch" ] ||
git merge-base --is-ancestor $remote/master HEAD
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
changelog_title="fish $version (released $(date +'%B %d, %Y'))"
sed -i \
-e "1c$changelog_title" \
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
CHANGELOG.rst
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"
}
CommitVersion "$version" "Release $version"
# N.B. this is not GPG-signed.
git tag --annotate --message="Release $version" $version
git push $remote $version
TIMEOUT=
gh() {
command ${TIMEOUT:+timeout $TIMEOUT} \
gh --repo "$repository_owner/fish-shell" "$@"
}
gh workflow run release.yml --ref="$version" \
--raw-field="version=$version"
run_id=
while [ -z "$run_id" ] && sleep 5
do
run_id=$(gh run list \
--json=databaseId --jq=.[].databaseId \
--workflow=release.yml --limit=1 \
--commit="$(git rev-parse "$version^{commit}")")
done
# Update fishshell.com
tag_oid=$(git rev-parse "$version")
tmpdir=$(mktemp -d)
# TODO This works on draft releases only if "gh" is configured to
# have write access to the fish-shell repository. Unless we are fine
# publishing the release at this point, we should at least fail if
# "gh" doesn't have write access.
while ! \
gh release download "$version" --dir="$tmpdir" \
--pattern="fish-$version.tar.xz"
do
TIMEOUT=30 gh run watch "$run_id" ||:
sleep 5
done
actual_tag_oid=$(git ls-remote "$remote" |
awk '$2 == "refs/tags/'"$version"'" { print $1 }')
[ "$tag_oid" = "$actual_tag_oid" ]
( cd "$tmpdir" && tar xf fish-$version.tar.xz )
CopyDocs() {
rm -rf "$fish_site/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)
printf %s\\n "$releases" | tail -1
)
if [ "$version" = "$latest_release" ]; then
CopyDocs current
fi
rm -rf "$tmpdir"
(
cd "$fish_site"
make
git add -u
git add docs
if git ls-files --others --exclude-standard | grep .; then
exit 1
fi
git commit --message="$(printf %s "\
| Release $version (docs)
|
| Created by ../fish-shell/build_tools/release.sh
" | sed 's,^\s*| \?,,')"
)
gh_api_repo() {
path=$1
shift
command gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/$repository_owner/fish-shell/$path" \
"$@"
}
# Approve macos-codesign
# TODO what if current user can't approve?
gh_pending_deployments() {
gh_api_repo "actions/runs/$run_id/pending_deployments" "$@"
}
while {
environment_id=$(gh_pending_deployments | jq .[].environment.id)
[ -z "$environment_id" ]
}
do
sleep 5
done
echo '
{
"environment_ids": ['"$environment_id"'],
"state": "approved",
"comment": "Approved via ./build_tools/release.sh"
}
' |
gh_pending_deployments --method POST --input=-
# Await completion.
gh run watch "$run_id"
while {
! draft=$(gh release view "$version" --json=isDraft --jq=.isDraft) \
|| [ "$draft" = true ]
}
do
sleep 20
done
(
cd "$fish_site"
make new-release
git add -u
git add docs
if git ls-files --others --exclude-standard | grep .; then
exit 1
fi
git commit --message="$(printf %s "\
| Release $version (release list update)
|
| Created by ../fish-shell/build_tools/release.sh
" | sed 's,^\s*| \?,,')"
# 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
)
if [ -n "$integration_branch" ]; then {
git push $remote "$version^{commit}":refs/heads/$integration_branch
} else {
changelog=$(cat - CHANGELOG.rst <<EOF
fish ?.?.? (released ???)
=========================
EOF
)
printf %s\\n "$changelog" >CHANGELOG.rst
CommitVersion ${version}-snapshot "start new cycle"
git push $remote HEAD:master
} fi
milestone_number=$(
gh_api_repo milestones?state=open |
jq '.[] | select(.title == "fish '"$version"'") | .number'
)
gh_api_repo milestones/$milestone_number --method PATCH \
--raw-field state=closed
next_patch_version=$(
echo "$version" | awk -F. '
NF == 3 && $3 ~ /[0-9]+/ {
printf "%s.%s.%s", $1, $2, $3+1
}
'
)
if [ -n "$next_patch_version" ]; then
gh_api_repo milestones --method POST \
--raw-field title="fish $next_patch_version"
fi
exit
}

View File

@@ -1,53 +1,38 @@
#!/usr/bin/env fish
#
# This runs Python files, fish scripts (*.fish), and Rust files
# through their respective code formatting programs.
# This runs C++ files and fish scripts (*.fish) 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
if test "$argv[1]" = --all
set all yes
if set -q argv[1]
echo "Unexpected arguments: '$argv'"
exit 1
end
set -e argv[1]
end
set -l workspace_root (status dirname)/..
if set -q argv[1]
echo "Unexpected arguments: '$argv'"
exit 1
end
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
set -l files (git status --porcelain --short --untracked-files=all | sed -e 's/^ *[^ ]* *//')
if set -q files[1]
echo
echo 'You have uncommitted changes. 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
set fish_files $workspace_root/{benchmarks,build_tools,etc,share}/**.fish
set python_files $workspace_root
set fish_files share/**.fish
set python_files {doc_src,share,tests}/**.py
set rust_files fish-rust/src/**.rs
else
# Format the files specified as arguments.
set -l files $argv
# Extract just the fish files.
set fish_files (string match -r '^.*\.fish$' -- $files)
set python_files (string match -r '^.*\.py$' -- $files)
set rust_files (string match -r '^.*\.rs$' -- $files)
@@ -55,70 +40,37 @@ end
set -l red (set_color red)
set -l green (set_color green)
set -l yellow (set_color yellow)
set -l blue (set_color blue)
set -l normal (set_color normal)
# Run the fish reformatter if we have any fish files.
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
make fish_indent
set PATH . $PATH
end
echo === Running "$green"fish_indent"$normal"
if set -l -q _flag_check
if not fish_indent --check -- $fish_files
echo $red"Fish files are not formatted correctly."$normal
exit 1
end
else
fish_indent -w -- $fish_files
end
fish_indent -w -- $fish_files
end
if set -q python_files[1]
if not type -q ruff
if not type -q black
echo
echo Please install "`black`" to style python
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
if not ruff format --check $python_files
echo $red"Python files are not formatted correctly."$normal
exit 1
end
else
ruff format $python_files
echo === Running "$blue"black"$normal"
black $python_files
end
end
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
echo === Running "$green"rustfmt"$normal"
if set -l -q _flag_check
if set -l -q _flag_all
if not cargo fmt --all --check
echo $red"Rust files are not formatted correctly."$normal
exit 1
end
if set -q rust_files[1]
if not type -q rustfmt
echo
echo Please install "`rustfmt`" to style rust
echo
else
if set -q rust_files[1]
if not rustfmt --check --files-with-diff $rust_files
echo $red"Rust files are not formatted correctly."
exit 1
end
end
end
else
if set -l -q _flag_all
cargo fmt --all
else
if set -q rust_files[1]
rustfmt $rust_files
end
echo === Running "$blue"rustfmt"$normal"
rustfmt $rust_files
end
end

View File

@@ -1,42 +1,36 @@
#!/usr/bin/env fish
# Updates the files used for gettext translations.
# By default, the whole xgettext + msgmerge pipeline runs,
# By default, the whole xgettext, msgmerge, msgfmt pipeline runs,
# which extracts the messages from the source files into $template_file,
# and updates the PO files for each language from that.
# updates the PO files for each language from that
# (changed line numbers, added messages, removed messages),
# and finally generates a machine-readable MO file for each language,
# which is stored in share/locale/$LANG/LC_MESSAGES/fish.mo (relative to the repo root).
#
# Use cases:
# For developers:
# - Run with no args to update all PO files after making changes to Rust/fish sources.
# - Run with args `--no-mo` to update all PO files after making changes to Rust/fish
# sources.
# For translators:
# - Run with `--no-mo` first, to ensure that the strings you are translating are up to date.
# - 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 template_file $build_tools/../po/template.po
set -l po_dir $build_tools/../po
set -l extract
set -l po
set -l mo
argparse dry-run use-existing-template= -- $argv
argparse --exclusive 'no-mo,only-mo' 'no-mo' 'only-mo' -- $argv
or exit $status
if test -z $argv[1]
@@ -45,8 +39,8 @@ if test -z $argv[1]
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"
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg))
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 ""
@@ -56,7 +50,7 @@ else
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$'
if not basename $arg | grep -qE '^[a-z]{2}(_[A-Z]{2})?\.po$'
echo "Filename does not match the expected format ('ll.po' or 'll_CC.po')."
exit 1
end
@@ -64,94 +58,31 @@ else
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
if set -l --query _flag_no_mo
set -l --erase mo
end
if set -l --query _flag_only_mo
set -l --erase extract
set -l --erase po
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
$build_tools/fish_xgettext.fish >$template_file
or exit 1
end
for po_file in $po_files
if set --query tmpdir[1]
set po_file $tmpdir/(basename $po_file)
end
if set -l --query po
if test -e $po_file
merge_po_files $template_file $po_file
msgmerge --update --no-fuzzy-matching --no-wrap --backup=none $po_file $template_file
else
begin
print_header
cat $template_file
end >$po_file
cp $template_file $po_file
end
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
if set -l --query mo
set -l locale_dir $build_tools/../share/locale
set -l out_dir $locale_dir/(basename $po_file .po)/LC_MESSAGES
mkdir -p $out_dir
msgfmt --check-format --output-file=$out_dir/fish.mo $po_file
end
end
cleanup_exit

View File

@@ -37,12 +37,10 @@ set(MANUALS ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish.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
)
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-faq.1)
# Determine which man page we don't want to install.
# On OS X, don't install a man page for open, since we defeat fish's open
@@ -156,6 +154,16 @@ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/html/ # Trailing slash is
DESTINATION ${docdir} OPTIONAL)
install(FILES CHANGELOG.rst DESTINATION ${docdir})
# These files are built by cmake/gettext.cmake, but using GETTEXT_PROCESS_PO_FILES's
# INSTALL_DESTINATION leads to them being installed as ${lang}.gmo, not fish.mo
# The ${languages} array comes from cmake/gettext.cmake
if(GETTEXT_FOUND)
foreach(lang ${languages})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${lang}.gmo DESTINATION
${CMAKE_INSTALL_LOCALEDIR}/${lang}/LC_MESSAGES/ RENAME fish.mo)
endforeach()
endif()
# Group install targets into a InstallTargets folder
set_property(TARGET build_fish_pc CHECK-FISH-BUILD-VERSION-FILE
PROPERTY FOLDER cmake/InstallTargets)

View File

@@ -24,7 +24,7 @@ add_executable(fish_macapp EXCLUDE_FROM_ALL
# Compute the version. Note this is done at generation time, not build time,
# 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.
# generally this will be run by make_pkg.sh which always re-runs cmake.
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
COMMAND cut -d- -f1
@@ -32,7 +32,7 @@ execute_process(
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Note CMake appends .app, so the real output name will be fish.app.
# Note CMake appends .app, so the real output name will be fish.app.
# This target does not include the 'base' resource.
set_target_properties(fish_macapp PROPERTIES OUTPUT_NAME "fish")

View File

@@ -5,11 +5,11 @@ set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo/build")
if(DEFINED ASAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
list(APPEND FISH_CARGO_FEATURES_LIST "asan")
list(APPEND FISH_CRATE_FEATURES "asan")
endif()
if(DEFINED TSAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
list(APPEND FISH_CARGO_FEATURES_LIST "tsan")
list(APPEND FISH_CRATE_FEATURES "tsan")
endif()
if (Rust_CARGO_TARGET)
@@ -21,24 +21,32 @@ endif()
set(rust_profile $<IF:$<CONFIG:Debug>,debug,$<IF:$<CONFIG:RelWithDebInfo>,release-with-debug,release>>)
set(rust_debugflags "$<$<CONFIG:Debug>:-g>$<$<CONFIG:RelWithDebInfo>:-g>")
option(WITH_GETTEXT "Build with gettext localization support. Requires `msgfmt` to work." ON)
# Enable gettext feature unless explicitly disabled.
if(NOT DEFINED WITH_GETTEXT OR "${WITH_GETTEXT}")
list(APPEND FISH_CARGO_FEATURES_LIST "localize-messages")
# Temporary hack to propagate CMake flags/options to build.rs. We need to get CMake to evaluate the
# truthiness of the strings if they are set.
set(CMAKE_WITH_GETTEXT "1")
if(DEFINED WITH_GETTEXT AND NOT "${WITH_GETTEXT}")
set(CMAKE_WITH_GETTEXT "0")
endif()
list(JOIN FISH_CARGO_FEATURES_LIST , FISH_CARGO_FEATURES)
if(FISH_CRATE_FEATURES)
set(FEATURES_ARG ${FISH_CRATE_FEATURES})
list(PREPEND FEATURES_ARG "--features")
endif()
# Tell Cargo where our build directory is so it can find Cargo.toml.
set(VARS_FOR_CARGO
"FISH_BUILD_DIR=${CMAKE_BINARY_DIR}"
"PREFIX=${CMAKE_INSTALL_PREFIX}"
# Temporary hack to propagate CMake flags/options to build.rs.
"CMAKE_WITH_GETTEXT=${CMAKE_WITH_GETTEXT}"
# Cheesy so we can tell cmake was used to build
"CMAKE=1"
"DOCDIR=${CMAKE_INSTALL_FULL_DOCDIR}"
"DATADIR=${CMAKE_INSTALL_FULL_DATADIR}"
"SYSCONFDIR=${CMAKE_INSTALL_FULL_SYSCONFDIR}"
"BINDIR=${CMAKE_INSTALL_FULL_BINDIR}"
"LOCALEDIR=${CMAKE_INSTALL_FULL_LOCALEDIR}"
"CARGO_TARGET_DIR=${FISH_RUST_BUILD_DIR}"
"CARGO_BUILD_RUSTC=${Rust_COMPILER}"
"${FISH_PCRE2_BUILDFLAG}"

View File

@@ -1,33 +1,92 @@
# This adds ctest support to the project
enable_testing()
# By default, ctest runs tests serially
# Support CTEST_PARALLEL_LEVEL as an environment variable in addition to a CMake variable
if(NOT CTEST_PARALLEL_LEVEL)
set(CTEST_PARALLEL_LEVEL $ENV{CTEST_PARALLEL_LEVEL})
if(NOT CTEST_PARALLEL_LEVEL)
include(ProcessorCount)
ProcessorCount(CORES)
math(EXPR halfcores "${CORES} / 2")
set(CTEST_PARALLEL_LEVEL ${halfcores})
endif()
endif()
# Put in a tests folder to reduce the top level targets in IDEs.
set(CMAKE_FOLDER tests)
# We will use 125 as a reserved exit code to indicate that a test has been skipped, i.e. it did not
# pass but it should not be considered a failed test run, either.
set(SKIP_RETURN_CODE 125)
# Even though we are using CMake's ctest for testing, we still define our own `make fish_run_tests` target
# rather than use its default for many reasons:
# * CMake doesn't run tests in-proc or even add each tests as an individual node in the ninja
# dependency tree, instead it just bundles all tests into a target called `test` that always just
# shells out to `ctest`, so there are no build-related benefits to not doing that ourselves.
# * CMake devs insist that it is appropriate for `make fish_run_tests` to never depend on `make all`, i.e.
# running `make fish_run_tests` does not require any of the binaries to be built before testing.
# * It is not possible to set top-level CTest options/settings such as CTEST_PARALLEL_LEVEL from
# within the CMake configuration file.
# * The only way to have a test depend on a binary is to add a fake test with a name like
# "build_fish" that executes CMake recursively to build the `fish` target.
# * Circling back to the point about individual tests not being actual Makefile targets, CMake does
# not offer any way to execute a named test via the `make`/`ninja`/whatever interface; the only
# way to manually invoke test `foo` is to to manually run `ctest` and specify a regex matching
# `foo` as an argument, e.g. `ctest -R ^foo$`... which is really crazy.
# The top-level test target is "fish_run_tests".
add_custom_target(fish_run_tests
COMMAND env CTEST_PARALLEL_LEVEL=${CTEST_PARALLEL_LEVEL} FISH_FORCE_COLOR=1
${CMAKE_CTEST_COMMAND} --force-new-ctest-process # --verbose
--output-on-failure --progress
DEPENDS fish fish_indent fish_key_reader fish_test_helper
USES_TERMINAL
)
# CMake being CMake, you can't just add a DEPENDS argument to add_test to make it depend on any of
# your binaries actually being built before `make fish_run_tests` is executed (requiring `make all` first),
# and the only dependency a test can have is on another test. So we make building fish
# prerequisites to our entire top-level `test` target.
function(add_test_target NAME)
string(REPLACE "/" "-" NAME ${NAME})
add_custom_target("test_${NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R "^${NAME}$$"
DEPENDS fish fish_indent fish_key_reader fish_test_helper USES_TERMINAL)
endfunction()
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}
get_filename_component(CHECK ${CHECK} NAME_WE)
add_test(NAME ${CHECK_NAME}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py --cachedir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK}.fish
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader fish_test_helper
USES_TERMINAL
)
set_tests_properties(${CHECK_NAME} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
set_tests_properties(${CHECK_NAME} PROPERTIES ENVIRONMENT FISH_FORCE_COLOR=1)
add_test_target("${CHECK_NAME}")
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}
add_test(NAME ${PEXPECT}
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py --cachedir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
DEPENDS fish fish_indent fish_key_reader fish_test_helper
USES_TERMINAL
)
set_tests_properties(${PEXPECT} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
set_tests_properties(${PEXPECT} PROPERTIES ENVIRONMENT FISH_FORCE_COLOR=1)
add_test_target("${PEXPECT}")
endforeach(PEXPECT)
# Rust stuff.
set(cargo_test_flags)
# Rust stuff.
if(DEFINED ASAN)
# Rust w/ -Zsanitizer=address requires explicitly specifying the --target triple or else linker
# errors pertaining to asan symbols will ensue.
@@ -48,17 +107,10 @@ if(DEFINED Rust_CARGO_TARGET)
list(APPEND cargo_test_flags "--lib")
endif()
set(max_concurrency_flag)
if(DEFINED ENV{FISH_TEST_MAX_CONCURRENCY})
list(APPEND max_concurrency_flag "--max-concurrency" $ENV{FISH_TEST_MAX_CONCURRENCY})
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} 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
add_test(
NAME "cargo-test"
COMMAND env ${VARS_FOR_CARGO} cargo test --no-default-features ${CARGO_FLAGS} --workspace --target-dir ${rust_target_dir} ${cargo_test_flags}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
set_tests_properties("cargo-test" PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
add_test_target("cargo-test")

22
cmake/gettext.cmake Normal file
View File

@@ -0,0 +1,22 @@
set(languages de en fr pl pt_BR sv zh_CN)
include(FeatureSummary)
option(WITH_GETTEXT "translate messages if gettext is available" ON)
if(WITH_GETTEXT)
find_package(Gettext)
endif()
add_feature_info(gettext GETTEXT_FOUND "translate messages with gettext")
# Define translations
if(GETTEXT_FOUND)
# Group pofile targets into their own folder, as there's a lot of them.
set(CMAKE_FOLDER pofiles)
foreach(lang ${languages})
# Our translations aren't set up entirely as CMake expects, so installation is done in
# cmake/Install.cmake instead of using INSTALL_DESTINATION
gettext_process_po_files(${lang} ALL
PO_FILES po/${lang}.po)
endforeach()
set(CMAKE_FOLDER)
endif()

View File

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

View File

@@ -1,48 +0,0 @@
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) {
Ok(p) => return Some(p),
Err(err) => err,
};
use env::VarError::*;
match err {
NotPresent => None,
NotUnicode(os_string) => {
panic!(
"Environment variable {name} is not valid Unicode: {:?}",
os_string.as_bytes()
)
}
}
}
pub fn workspace_root() -> &'static Path {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
manifest_dir.ancestors().nth(2).unwrap()
}
fn cargo_target_dir() -> Cow<'static, Path> {
option_env!("CARGO_TARGET_DIR")
.map(|d| Cow::Borrowed(Path::new(d)))
.unwrap_or(Cow::Owned(workspace_root().join("target")))
}
pub fn fish_build_dir() -> Cow<'static, Path> {
// This is set if using CMake.
option_env!("FISH_BUILD_DIR")
.map(|d| Cow::Borrowed(Path::new(d)))
.unwrap_or(cargo_target_dir())
}
// 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());
}
// TODO Move this to rsconf
pub fn rebuild_if_paths_changed<P: AsRef<Path>, I: IntoIterator<Item = P>>(paths: I) {
for path in paths {
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());
}
}

View File

@@ -1,13 +0,0 @@
[package]
name = "fish-build-man-pages"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -1,95 +0,0 @@
#[cfg(not(clippy))]
use std::path::Path;
fn main() {
let mandir = fish_build_helper::fish_build_dir().join("fish-man");
let sec1dir = mandir.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(sec1dir.to_str().unwrap());
#[cfg(not(clippy))]
build_man(&mandir);
}
#[cfg(not(clippy))]
fn build_man(man_dir: &Path) {
use std::process::{Command, Stdio};
use fish_build_helper::{env_var, workspace_root};
let workspace_root = workspace_root();
let man_str = man_dir.to_str().unwrap();
let sec1_dir = man_dir.join("man1");
let sec1_str = sec1_dir.to_str().unwrap();
let docsrc_dir = workspace_root.join("doc_src");
let docsrc_str = docsrc_dir.to_str().unwrap();
let sphinx_doc_sources = [
workspace_root.join("CHANGELOG.rst"),
workspace_root.join("CONTRIBUTING.rst"),
docsrc_dir.clone(),
];
fish_build_helper::rebuild_if_paths_changed(sphinx_doc_sources);
let args = &[
"-j", "auto", "-q", "-b", "man", "-c", docsrc_str,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d", man_str, docsrc_str, sec1_str,
];
let _ = std::fs::create_dir_all(sec1_str);
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
// We run sphinx to build the man pages.
// Every error here is fatal so cargo doesn't cache the result
// - if we skipped the docs with sphinx not installed, installing it would not then build the docs.
// That means you need to explicitly set $FISH_BUILD_DOCS=0 (`FISH_BUILD_DOCS=0 cargo install --path .`),
// which is unfortunate - but the docs are pretty important because they're also used for --help.
let sphinx_build = match Command::new("sphinx-build")
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if env_var("FISH_BUILD_DOCS") == Some("1".to_string()) {
panic!("Could not find sphinx-build to build man pages.\nInstall sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0.");
}
rsconf::warn!("Cannot find sphinx-build to build man pages.");
rsconf::warn!("If you install it now you need to run `cargo clean` and rebuild, or set $FISH_BUILD_DOCS=1 explicitly.");
return;
}
Err(e) => {
// Another error - permissions wrong etc
panic!("Error starting sphinx-build to build man pages: {:?}", e);
}
Ok(sphinx_build) => sphinx_build,
};
match sphinx_build.wait_with_output() {
Err(err) => {
panic!(
"Error waiting for sphinx-build to build man pages: {:?}",
err
);
}
Ok(out) => {
if !out.stderr.is_empty() {
rsconf::warn!("sphinx-build: {}", String::from_utf8_lossy(&out.stderr));
}
assert_eq!(&String::from_utf8_lossy(&out.stdout), "");
if !out.status.success() {
panic!("sphinx-build failed to build the man pages.");
}
}
}
}

View File

@@ -1 +0,0 @@

View File

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

View File

@@ -1,107 +0,0 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use std::{ffi::OsString, fs::OpenOptions, io::Write};
fn unescape_multiline_rust_string(s: String) -> String {
if !s.contains('\n') {
return s;
}
let mut unescaped = String::new();
enum State {
Ground,
Escaped,
ContinuationLineLeadingWhitespace,
}
use State::*;
let mut state = Ground;
for c in s.chars() {
match state {
Ground => match c {
'\\' => state = Escaped,
_ => {
unescaped.push(c);
}
},
Escaped => match c {
'\\' => {
unescaped.push('\\');
state = Ground
}
'\n' => state = ContinuationLineLeadingWhitespace,
_ => panic!("Unsupported escape sequence '\\{c}' in message string '{s}'"),
},
ContinuationLineLeadingWhitespace => match c {
' ' | '\t' => (),
_ => {
unescaped.push(c);
state = Ground
}
},
}
}
unescaped
}
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());
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"
} else {
""
};
let po_entry = format!("{format_string_annotation}msgid {message_string}\nmsgstr \"\"\n\n");
file.write_all(po_entry.as_bytes()).unwrap();
}
/// The `message` is passed through unmodified.
/// 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_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(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.");
if token_trees.next().is_some() {
panic!("Invalid number of tokens passed to gettext_extract. Expected one token, but got more.")
}
if let proc_macro2::TokenTree::Group(group) = first_token {
let mut group_tokens = group.stream().into_iter();
let first_group_token = group_tokens
.next()
.expect("gettext_extract expected one group token but got none.");
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 {
append_po_entry_to_file(&message, &file_path);
} else {
panic!("Expected literal in gettext_extract, but got: {first_group_token:?}");
}
} else {
panic!("Expected group in gettext_extract, but got: {first_token:?}");
}
}
message
}

View File

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

View File

@@ -1,153 +0,0 @@
use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Stdio},
};
use fish_build_helper::env_var;
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::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},
};
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();
let localization_map_path =
Path::new(&env_var("OUT_DIR").unwrap()).join("localization_maps.rs");
let mut localization_map_file = BufWriter::new(File::create(&localization_map_path).unwrap());
// This will become a map which maps from language identifiers to maps containing localizations
// for the respective language.
let mut catalogs = phf_codegen::Map::new();
match Command::new("msgfmt")
.arg("-h")
.stdout(Stdio::null())
.status()
{
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
rsconf::warn!(
"Cannot find msgfmt to build gettext message catalogs. Localization will not work."
);
rsconf::warn!(
"If you install it now you need to trigger a rebuild to get localization support."
);
rsconf::warn!(
"One way to achieve that is running `touch po` followed by the build command."
);
}
Err(e) => {
panic!("Error when trying to run `msgfmt -h`: {e:?}");
}
Ok(_) => {
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 lang = po_file_path
.file_stem()
.expect("All entries in the po directory must be regular files.");
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(lang);
// Include the file containing the map for this language in the main generated file.
writeln!(
&mut localization_map_file,
"include!(\"{}\");",
cached_map_path.display()
)
.unwrap();
// Map from the language identifier to the map containing the localizations for this
// language.
catalogs.entry(language, format!("&{map_name}"));
if let Ok(metadata) = std::fs::metadata(&cached_map_path) {
// Cached map file exists, but might be outdated.
let cached_map_mtime = metadata.modified().unwrap();
let po_mtime = dir_entry.metadata().unwrap().modified().unwrap();
if cached_map_mtime > po_mtime {
// Cached map file is considered up-to-date.
continue;
};
}
// Generate the map file.
// Try to create new MO data and load it into `mo_data`.
let output = Command::new("msgfmt")
.arg("--check-format")
.arg("--output-file=-")
.arg(&po_file_path)
.output()
.unwrap();
if !output.status.success() {
panic!(
"msgfmt failed:\n{}",
String::from_utf8(output.stderr).unwrap()
);
}
let mo_data = output.stdout;
// Extract map from MO data.
let language_localizations = parse_mo_file(&mo_data).unwrap();
// This file will contain the localization map for the current language.
let mut cached_map_file = File::create(&cached_map_path).unwrap();
let mut single_language_localization_map = phf_codegen::Map::new();
// The values will be written into the source code as is, meaning escape sequences and
// double quotes in the data will be interpreted by the Rust compiler, which is undesirable.
// Converting them to raw strings prevents this. (As long as no input data contains `"###`.)
fn to_raw_str(s: &str) -> String {
assert!(!s.contains("\"###"));
format!("r###\"{s}\"###")
}
for (msgid, msgstr) in language_localizations {
single_language_localization_map.entry(
String::from_utf8(msgid.into()).unwrap(),
to_raw_str(&String::from_utf8(msgstr.into()).unwrap()),
);
}
writeln!(&mut cached_map_file, "#[allow(non_upper_case_globals)]").unwrap();
write!(
&mut cached_map_file,
"static {}: phf::Map<&'static str, &'static str> = {}",
&map_name,
single_language_localization_map.build()
)
.unwrap();
writeln!(&mut cached_map_file, ";").unwrap();
}
}
}
write!(
&mut localization_map_file,
"pub static CATALOGS: phf::Map<&str, &phf::Map<&str, &str>> = {}",
catalogs.build()
)
.unwrap();
writeln!(&mut localization_map_file, ";").unwrap();
}

View File

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

View File

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

View File

@@ -1,131 +0,0 @@
use std::collections::HashMap;
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())
}
fn read_be_u32(bytes: &[u8]) -> u32 {
u32::from_be_bytes(bytes[..U32_SIZE].try_into().unwrap())
}
fn get_u32_reader_from_magic_number(magic_number: &[u8]) -> std::io::Result<fn(&[u8]) -> u32> {
match magic_number {
[0x95, 0x04, 0x12, 0xde] => Ok(read_be_u32),
[0xde, 0x12, 0x04, 0x95] => Ok(read_le_u32),
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"First 4 bytes of MO file must correspond to magic number 0x950412de, either big or little endian.",
)),
}
}
/// Returns an error if an unknown major revision is detected.
/// There are no relevant differences between supported revisions.
fn check_if_revision_is_supported(revision: u32) -> std::io::Result<()> {
// From the reference:
// A program seeing an unexpected major revision number should stop reading the MO file entirely;
// whereas an unexpected minor revision number means that the file can be read
// but will not reveal its full contents,
// when parsed by a program that supports only smaller minor revision numbers.
let major_revision = revision >> 16;
match major_revision {
0 | 1 => {
// At time of writing, these are the only major revisions which exist.
// There is no documented difference and the GNU gettext code does not seem to
// differentiate between the two either.
// All features we care about are supported in minor revision 0,
// so we do not need to care about the minor revision.
Ok(())
}
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Major revision must be 0 or 1",
)),
}
}
fn as_usize(value: u32) -> usize {
use std::mem::size_of;
const _: () = assert!(size_of::<u32>() <= size_of::<usize>());
usize::try_from(value).unwrap()
}
fn parse_strings(
file_content: &[u8],
num_strings: usize,
table_offset: usize,
read_u32: fn(&[u8]) -> u32,
) -> std::io::Result<Vec<&[u8]>> {
let file_too_short_error = || {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"MO file is too short.",
))
};
if table_offset + num_strings * 2 * U32_SIZE > file_content.len() {
return file_too_short_error();
}
let mut strings = Vec::with_capacity(num_strings);
let mut offset = table_offset;
let mut get_next_u32 = || {
let val = read_u32(&file_content[offset..]);
offset += U32_SIZE;
val
};
for _ in 0..num_strings {
// not including NUL terminator
let string_length = as_usize(get_next_u32());
let string_offset = as_usize(get_next_u32());
let string_end = string_offset.checked_add(string_length).unwrap();
if string_end > file_content.len() {
return file_too_short_error();
}
// Contexts are stored by storing the concatenation of the context, a EOT byte, and the original string, instead of the original string.
// Contexts are not supported by this implementation.
// The format allows plural forms to appear behind singular forms, separated by a NUL byte,
// where `string_length` includes the length of both.
// This is not supported here.
// Do not include the NUL terminator in the slice.
strings.push(&file_content[string_offset..string_end]);
}
Ok(strings)
}
/// Parse a MO file.
/// Format reference used: <https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html>
pub fn parse_mo_file(file_content: &[u8]) -> std::io::Result<HashMap<&[u8], &[u8]>> {
if file_content.len() < 7 * U32_SIZE {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"File too short to contain header.",
));
}
// The first 4 bytes are a magic number, from which the endianness can be determined.
let read_u32 = get_u32_reader_from_magic_number(&file_content[0..U32_SIZE])?;
let mut offset = U32_SIZE;
let mut get_next_u32 = || {
let val = read_u32(&file_content[offset..]);
offset += U32_SIZE;
val
};
let file_format_revision = get_next_u32();
check_if_revision_is_supported(file_format_revision)?;
let num_strings = as_usize(get_next_u32());
let original_strings_offset = as_usize(get_next_u32());
let translation_strings_offset = as_usize(get_next_u32());
let original_strings =
parse_strings(file_content, num_strings, original_strings_offset, read_u32)?;
let translated_strings = parse_strings(
file_content,
num_strings,
translation_strings_offset,
read_u32,
)?;
let mut translation_map = HashMap::with_capacity(num_strings);
for i in 0..num_strings {
translation_map.insert(original_strings[i], translated_strings[i]);
}
Ok(translation_map)
}

6
debian/control vendored
View File

@@ -23,10 +23,10 @@ Architecture: any
# for col and lock - bsdmainutils is required in Ubuntu focal
Depends: bsdextrautils | bsdmainutils,
file,
# for the msgfmt command
# for the gettext command
gettext-base,
# for man
man-db,
# for nroff and preconv
groff-base,
# for terminal definitions
ncurses-base,
# for kill

2
debian/rules vendored
View File

@@ -17,7 +17,7 @@ override_dh_auto_configure:
dh_auto_configure --buildsystem=cmake -- -DCMAKE_BUILD_TYPE=RelWithDebInfo
override_dh_clean:
dh_clean --exclude=Cargo.toml.orig
dh_clean
-unlink .cargo
-unlink vendor

View File

@@ -23,10 +23,10 @@ Steps:
## Building locally (no code signing)
To build locally without notarizing and code signing, use the `build_tools/make_macos_pkg.sh` script:
To build locally without notarizing and code signing, use the `build_tools/make_pkg.sh` script:
```
> ./build_tools/make_macos_pkg.sh
> ./build_tools/make_pkg.sh
```
Packages will be placed in `~/fish_built` by default.
@@ -45,7 +45,7 @@ You will need the following:
An example run:
```
> ./build_tools/make_macos_pkg.sh -s \
> ./build_tools/make_pkg.sh -s \
-f fish-developer-id-application.p12 \
-i fish-developer-id-installer.p12 \
-p "$NOTARIZE_PASSWORD" \

View File

@@ -15,15 +15,12 @@ Description
``_`` translates its arguments into the current language, if possible.
This only works with messages which are translated as part of fish's own sources, so using it as part of your own fish scripts which are not upstreamed into the fish repo will not work unless the exact same message also exists upstream.
It is equivalent to ``gettext fish STRING``, meaning it can only be used to look up fish's own translations.
It requires fish to be built with gettext support. If that support is disabled or there is no translation it will echo the argument back.
It requires fish to be built with gettext support. If that support is disabled, or there is no translation it will echo the argument back.
The language depends on the current locale, set with :envvar:`LANG`, :envvar:`LC_MESSAGES`, :envvar:`LC_ALL`, and :envvar:`LANGUAGE`.
These variables do not have to be exported for fish to use them, and fish's variable scopes are supported.
If other programs launched via fish should respect these locale variables they have to be exported to make them available outside of fish.
The language depends on the current locale, set with :envvar:`LANG` and :envvar:`LC_MESSAGES`.
For :envvar:`LANGUAGE` you can use a list, or use colons to separate multiple languages.
Options
-------
@@ -33,20 +30,7 @@ Options
Examples
--------
Use German translations::
::
> set LANG de_DE.UTF-8
> _ file
> _ File
Datei
Specify a precedence of languages (only works with :envvar:`LANGUAGE`)::
> set LANGUAGE pt de
> _ file # This message has a Portuguese translation.
arquivo
> _ "Invalid arguments" # This message does not have a Portuguese translation, but a German one.
Ungültige Argumente
> _ untranslatable # No translation in Portuguese, nor in German.
untranslatable
Note that the specific examples may change if translations are added/modified.

View File

@@ -14,7 +14,7 @@ Synopsis
Description
-----------
This command makes it easy for fish scripts and functions to handle arguments. You pass arguments that define the known options, followed by a literal **--**, then the arguments to be parsed (which might also include a literal **--**). ``argparse`` then sets variables to indicate the passed options with their values, sets ``$argv_opts`` to the options and their values, and sets ``$argv`` to the remaining arguments. See the :ref:`usage <cmd-argparse-usage>` section below.
This command makes it easy for fish scripts and functions to handle arguments. You pass arguments that define the known options, followed by a literal **--**, then the arguments to be parsed (which might also include a literal **--**). ``argparse`` then sets variables to indicate the passed options with their values, and sets ``$argv`` to the remaining arguments. See the :ref:`usage <cmd-argparse-usage>` section below.
Each option specification (``OPTION_SPEC``) is written in the :ref:`domain specific language <cmd-argparse-option-specification>` described below. All OPTION_SPECs must appear after any argparse flags and before the ``--`` that separates them from the arguments to be parsed.
@@ -27,8 +27,8 @@ Options
The following ``argparse`` options are available. They must appear before all *OPTION_SPEC*\ s:
**-n** or **--name** *NAME*
Use *NAME* in error messages. By default the current function name will be used, or ``argparse`` if run outside of a function.
**-n** or **--name**
The command name for use in error messages. By default the current function name will be used, or ``argparse`` if run outside of a function.
**-x** or **--exclusive** *OPTIONS*
A comma separated list of options that are mutually exclusive. You can use this more than once to define multiple sets of mutually exclusive options.
@@ -40,44 +40,8 @@ The following ``argparse`` options are available. They must appear before all *O
**-X** or **--max-args** *NUMBER*
The maximum number of acceptable non-option arguments. The default is infinity.
**-u** or **--move-unknown**
Allow unknown options, and move them from ``$argv`` to ``$argv_opts``. By default, Unknown options are treated as if they take optional arguments (i.e. have option spec ``=?``).
The above means that if a group of short options contains an unknown short option *followed* by a known short option, the known short option is
treated as an argument to the unknown one (e.g. ``--move-unknown h -- -oh`` will treat ``h`` as the argument to ``-o``, and so ``_flag_h`` will *not* be set).
In contrast, if the known option comes first (and does not take any arguments), the known option will be recognised (e.g. ``argparse --move-unknown h -- -ho`` *will* set ``$_flag_h`` to ``-h``)
**-i** or **--ignore-unknown**
Deprecated. This is like **--move-unknown**, except that unknown options and their arguments are kept in ``$argv`` and not moved to ``$argv_opts``. Unlike **--move-unknown**, this option makes it impossible to distinguish between an unknown option and non-option argument that starts with a ``-`` (since any ``--`` seperator in ``$argv`` will be removed).
**-S** or **--strict-longopts**
This makes the parsing of long options more strict. In particular, *without* this flag, if ``long`` is a known long option flag, ``--long`` and ``--long=<value>`` can be abbreviated as:
- ``-long`` and ``-long=<value>``, but *only* if there is no short flag ``l``.
- ``--lo`` and ``--lo=<value>``, but *only* if there is no other long flag that starts with ``lo``. Similarly with any other non-empty prefix of ``long``.
- ``-lo`` and ``-lo=<value>`` (i.e. combining the above two).
With the ``--strict-longopts`` flag, the above three are parse errors: one must use the syntax ``--long`` or ``--long=<value>`` to use a long option called ``long``.
This flag has no effect on the parsing of unknown options (which are parsed as if this flag is on).
This option may be on all the time in the future, so do not rely on the behaviour without it.
**--unknown-arguments** *KIND*
This option implies **--move-unknown**, unless **--ignore-unknown** is also given.
This will modify the parsing behaviour of unknown options depending on the value of *KIND*:
- **optional** (the default), allows each unknown option to take an optional argument (i.e. as if it had ``=?`` or ``=*`` in its option specification). For example, ``argparse --ignore-unknown --unknown-arguments=optional ab -- -u -a -ub`` will set ``_flag_a`` but *not* ``_flag_b``, as the ``b`` is treated as an argument to the second use of ``-u``.
- **required** requires each unknown option to take an argument (i.e. as if it had ``=`` or ``=+`` in its option specification). If the above example was changed to use ``--unknown-arguments=required``, *neither* ``_flag_a`` nor ``_flag_b`` would be set: the ``-a`` will be treated as an argument to the first use of ``-u``, and the ``b`` as an argument to the second.
- **none** forbids each unknown option from taking an argument (i.e. as if it had no ``=`` in its option specification). If the above example was changed to use ``--unknown-arguments=none``, *both* ``_flag_a`` and ``_flag_b`` would be set, as neither use of ``-u`` will be passed as taking an argument.
Note that the above assumes that unknown long flags use the ``--`` "GNU-style" (e.g. if *KIND* is ``none``, and there is no ``bar`` long option, ``-bar`` is interpreted as three short flags, ``b``, ``a``, and ``r``; but if ``bar`` is known, ``-bar`` is treated the same as ``--bar``).
When using ``--unknown-arguments=required``, you will get an error if the provided arguments end in an unknown option, since it has no argument. Similarly, with ``--unknown-arguments=none``, you will get an error if you use the ``--flag=value`` syntax and ``flag`` is an unknown option.
Ignores unknown options, keeping them and their arguments in $argv instead.
**-s** or **--stop-nonopt**
Causes scanning the arguments to stop as soon as the first non-option argument is seen. Among other things, this is useful to implement subcommands that have their own options.
@@ -127,7 +91,7 @@ But this is not::
set -l argv
argparse 'h/help' 'n/name' $argv
The first ``--`` seen is what allows the ``argparse`` command to reliably separate the option specifications and options to ``argparse`` itself (like ``--move-unknown``) from the command arguments, so it is required.
The first ``--`` seen is what allows the ``argparse`` command to reliably separate the option specifications and options to ``argparse`` itself (like ``--ignore-unknown``) from the command arguments, so it is required.
.. _cmd-argparse-option-specification:
@@ -136,13 +100,9 @@ Option Specifications
Each option specification consists of:
- An optional alphanumeric short flag character.
- An optional alphanumeric short flag character, followed by a ``/`` if the short flag can be used by someone invoking your command or, for backwards compatibility, a ``-`` if it should not be exposed as a valid short flag (in which case it will also not be exposed as a flag variable).
- An optional long flag name preceded by a ``/``. If neither a short flag nor long flag are present, an error is reported.
- If there is no short flag, and the long flag name is more than one character, the ``/`` can be omitted.
- For backwards compatibility, if there is a short and a long flag, a ``-`` can be used in place of the ``/``, if the short flag is not to be usable by users (in which case it will also not be exposed as a flag variable).
- An optional long flag name, which if not present the short flag can be used, and if that is also not present, an error is reported
- Nothing if the flag is a boolean that takes no argument or is an integer flag, or
@@ -150,15 +110,9 @@ Each option specification consists of:
- **=?** if it takes an optional value and only the last instance of the flag is saved, or
- **=+** if it requires a value and each instance of the flag is saved, or
- **=+** if it requires a value and each instance of the flag is saved.
- **=\*** if it takes an optional value *and* each instance of the flag is saved, storing the empty string when the flag was not given a value.
- Optionally a ``&``, indicating that the option and any attached values are not to be saved in ``$argv`` or ``$argv_opts``. This does not affect the the ``_flag_`` variables.
- Nothing if the flag is a boolean that takes no argument, or
- ``!`` followed by fish script to validate the value. Typically this will be a function to run. If the exit status is zero the value for the flag is valid. If non-zero the value is invalid. Any error messages should be written to stdout (not stderr). See the section on :ref:`Flag Value Validation <flag-value-validation>` for more information.
- Optionally a ``!`` followed by fish script to validate the value. Typically this will be a function to run. If the exit status is zero the value for the flag is valid. If non-zero the value is invalid. Any error messages should be written to stdout (not stderr). See the section on :ref:`Flag Value Validation <flag-value-validation>` for more information.
See the :doc:`fish_opt <fish_opt>` command for a friendlier but more verbose way to create option specifications.
@@ -178,7 +132,7 @@ This does not read numbers given as ``+NNN``, only those that look like flags -
Note: Optional arguments
------------------------
An option defined with ``=?`` or ``=*`` can take optional arguments. Optional arguments have to be *directly attached* to the option they belong to.
An option defined with ``=?`` can take optional arguments. Optional arguments have to be *directly attached* to the option they belong to.
That means the argument will only be used for the option if you use it like::
@@ -245,22 +199,16 @@ Some *OPTION_SPEC* examples:
- ``help`` means that only ``--help`` is valid. The flag is a boolean and can be used more than once. If it is used then ``_flag_help`` will be set as above. Also ``h-help`` (with an arbitrary short letter) for backwards compatibility.
- ``help&`` is similar (it will *remove* ``--help`` from ``$argv``), the difference is that ``--help``` will *not* placed in ``$argv_opts``.
- ``longonly=`` is a flag ``--longonly`` that requires an option, there is no short flag or even short flag variable.
- ``n/name=`` means that both ``-n`` and ``--name`` are valid. It requires a value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the single mandatory value associated with the flag.
- ``n/name=?`` means that both ``-n`` and ``--name`` are valid. It accepts an optional value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
- ``name=+`` means that only ``--name`` is valid. It requires a value and can be used more than once. If the flag is seen then ``_flag_name`` will be set with the values associated with each occurrence.
- ``x`` means that only ``-x`` is valid. It is a boolean that can be used more than once. If it is seen then ``_flag_x`` will be set as above.
- ``/x`` is similar, but only ``--x`` is valid (instead of ``-x``).
- ``x=``, ``x=?``, and ``x=+`` are similar to the n/name examples above but there is no long flag alternative to the short flag ``-x``.
- ``#max`` (or ``#-max``) means that flags matching the regex "^--?\\d+$" are valid. When seen they are assigned to the variable ``_flag_max``. This allows any valid positive or negative integer to be specified by prefixing it with a single "-". Many commands support this idiom. For example ``head -3 /a/file`` to emit only the first three lines of /a/file.
@@ -269,7 +217,7 @@ Some *OPTION_SPEC* examples:
- ``#longonly`` causes the last integer option to be stored in ``_flag_longonly``.
After parsing the arguments the ``argv`` variable is set with local scope to any values not already consumed during flag processing. If there are no unbound values the variable is set but ``count $argv`` will be zero. Similarly, the ``argv_opts`` variable is set with local scope to the arguments that *were* consumed during flag processing. This allows forwarding ``$argv_opts`` to another command, together with additional arguments.
After parsing the arguments the ``argv`` variable is set with local scope to any values not already consumed during flag processing. If there are no unbound values the variable is set but ``count $argv`` will be zero.
If an error occurs during argparse processing it will exit with a non-zero status and print error messages to stderr.
@@ -311,41 +259,17 @@ After this it figures out which variable it should operate on according to the `
and set $var $result
An example of using ``$argv_opts`` to forward known options to another command, whilst adding new options::
Limitations
-----------
function my-head
# The following option is the only existing one to head that takes arguments
# (we will forward it verbatim).
set -l opt_spec n/lines=
# --qwords is a new option, but --bytes is an existing one which we will modify below
set -a opt_spec "qwords=&" "c/bytes=&"
argparse --strict-longopts --move-unknown --unknown-arguments=none $opt_spec -- $argv || return
if set -q _flag_qwords
# --qwords allows specifying the size in multiples of 8 bytes
set -a argv_opts --bytes=(math -- $_flag_qwords \* 8 || return)
else if set -q _flag_bytes
# Allows using a 'q' suffix, e.g. --bytes=4q to mean 4*8 bytes.
if string match -qr 'q$' -- $_flag_bytes
set -a argv_opts --bytes=(math -- (string replace -r 'q$' '*8' -- $_flag_bytes) || return)
else
# Keep the users setting
set -a argv_opts --bytes=$_flag_bytes
end
One limitation with **--ignore-unknown** is that, if an unknown option is given in a group with known options, the entire group will be kept in $argv. ``argparse`` will not do any permutations here.
end
For instance::
if test (count $argv) -eq 0
# Default to heading /dev/kmsg (whereas head defaults to stdin)
set -l argv /dev/kmsg
end
argparse --ignore-unknown h -- -ho
echo $_flag_h # is -h, because -h was given
echo $argv # is still -ho
# Call the real head with our modified options and arguments.
head $argv_opts -- $argv
end
This limitation may be lifted in future.
The argparse call above saves all the options we do *not* want to process in ``$argv_opts``. (The ``--qwords`` and ``--bytes`` options are *not* saved there as their option spec's end in a ``~``). The code then processes the ``--qwords`` and ``--bytess`` options using the the ``$_flag_OPTION`` variables, and puts the transformed options in ``$argv_opts`` (which already contains all the original options, *other* than ``--qwords`` and ``--bytes``).
Note that because the ``argparse`` call above uses ``--move-unknown`` and ``--unknown-arguments=none``, we only need to tell it the arguments to ``head`` that take a value. This allows the wrapper script to accurately work out the *non*-option arguments (i.e. ``$argv``, the filenames that ``head`` is to operate on). Using ``--unknown-arguments=optional`` and explicitly listing all the known options to ``head`` however would have the advantage that if ``head`` were to add new options, they could still be used with the wrapper script using the "stuck" form for arguments (e.g. ``-o<arg>``, or ``--opt=<arg>``).
Note that the ``--strict-longopts`` is required to be able to correctly pass short options, e.g. without it ``my-head -q --bytes 10q``, will actually parse the ``-q`` as shorthand for ``--qwords``.
Additionally, it can only parse known options up to the first unknown option in the group - the unknown option could take options, so it isn't clear what any character after an unknown option means.

View File

@@ -42,6 +42,6 @@ The typical use is to run something, stop it with ctrl-z, and then continue it i
If only 123 and 789 exist, it will still background them and print an error about 456.
``bg 123 banana`` or ``bg banana 123`` will complain that "banana" is not a valid process ID.
``bg 123 banana`` or ``bg banana 123`` will complain that "banana" is not a valid job specifier.
``bg %2`` will background job 2.

View File

@@ -183,11 +183,8 @@ The following special input functions are available:
``clear-screen``
clears the screen and redraws the prompt.
.. _special-input-functions-scrollback-push:
``scrollback-push``
pushes earlier output to the terminal scrollback, positioning the prompt at the top.
This requires the terminal to implement the ECMA-48 :ref:`SCROLL UP <term-compat-indn>` command and :ref:`cursor position reporting <term-compat-cursor-position-report>`.
``complete``
guess the remainder of the current token

View File

@@ -19,7 +19,7 @@ Description
``break`` halts a currently running loop (*LOOP_CONSTRUCT*), such as a :doc:`for <for>` or :doc:`while <while>` loop. It is usually added inside of a conditional block such as an :doc:`if <if>` block.
The **-h** or **--help** option displays help about using this command.
There are no parameters for ``break``.
Example
-------

View File

@@ -15,8 +15,6 @@ Description
``continue`` skips the remainder of the current iteration of the current inner loop, such as a :doc:`for <for>` loop or a :doc:`while <while>` loop. It is usually added inside of a conditional block such as an :doc:`if <if>` statement or a :doc:`switch <switch>` statement.
The **-h** or **--help** option displays help about using this command.
Example
-------

View File

@@ -29,7 +29,7 @@ Currently supported are:
- ``wl-copy`` using wayland
- ``xsel`` and ``xclip`` for X11
- ``clip.exe`` on Windows.
- The :ref:`OSC 52 clipboard sequence <term-compat-osc-52>`, which your terminal might support
- The OSC 52 clipboard sequence, which your terminal might support
See also
--------

View File

@@ -8,7 +8,7 @@ Synopsis
.. synopsis::
fish_git_prompt [FORMAT]
fish_git_prompt
::
@@ -24,8 +24,6 @@ The ``fish_git_prompt`` function displays information about the current git repo
`Git <https://git-scm.com>`_ must be installed.
It is possible to modify the output format by passing an argument. The default value is ``" (%s)"``.
There are numerous customization options, which can be controlled with git options or fish variables. git options, where available, take precedence over the fish variable with the same function. git options can be set on a per-repository or global basis. git options can be set with the ``git config`` command, while fish variables can be set as usual with the :doc:`set <set>` command.
Boolean options (those which enable or disable something) understand "1", "yes" or "true" to mean true and every other value to mean false.
@@ -110,7 +108,7 @@ Variables used with ``showupstream`` (also implied by informative status):
- ``$__fish_git_prompt_char_upstream_ahead`` (>, ↑) - the character for the commits this repository is ahead of upstream
- ``$__fish_git_prompt_char_upstream_behind`` (<, ↓) - the character for the commits this repository is behind upstream
- ``$__fish_git_prompt_char_upstream_diverged`` (<>, ↓↑) - the symbol if this repository is both ahead and behind upstream
- ``$__fish_git_prompt_char_upstream_diverged`` (<>) - the symbol if this repository is both ahead and behind upstream
- ``$__fish_git_prompt_char_upstream_equal`` (=) - the symbol if this repo is equal to upstream
- ``$__fish_git_prompt_char_upstream_prefix`` ('')
- ``$__fish_git_prompt_color_upstream``

View File

@@ -43,6 +43,12 @@ The following options are available:
**--html**
Outputs HTML, which supports syntax highlighting if the appropriate CSS is defined. The CSS class names are the same as the variable names, such as ``fish_color_command``.
**-d** or **--debug=DEBUG_CATEGORIES**
Enable debug output and specify a pattern for matching debug categories. See :ref:`Debugging <debugging-fish>` in :doc:`fish <fish>` (1) for details.
**-o** or **--debug-output=DEBUG_FILE**
Specify a file path to receive the debug output, including categories and ``fish_trace``. The default is standard error.
**--dump-parse-tree**
Dumps information about the parsed statements to standard error. This is likely to be of interest only to people working on the fish source code.

View File

@@ -8,7 +8,7 @@ Synopsis
.. synopsis::
fish_opt [-s ALPHANUM] [-l LONG-NAME] [-ormd] [--long-only] [-v COMMAND OPTIONS ... ]
fish_opt [(-slor | --multiple-vals=) OPTNAME]
fish_opt --help
Description
@@ -18,14 +18,14 @@ This command provides a way to produce option specifications suitable for use wi
The following ``argparse`` options are available:
**-s** or **--short** *ALPHANUM*
Takes a single letter or number that is used as the short flag in the option being defined. Either this option or the **--long** option must be provided.
**-s** or **--short**
Takes a single letter that is used as the short flag in the option being defined. This option is mandatory.
**-l** or **--long** *LONG-NAME*
**-l** or **--long**
Takes a string that is used as the long flag in the option being defined. This option is optional and has no default. If no long flag is defined then only the short flag will be allowed when parsing arguments using the option specification.
**--long-only**
Deprecated. The option being defined will only allow the long flag name to be used, even if the short flag is defined (i.e., **--short** is specified).
The option being defined will only allow the long flag name to be used. The short flag name must still be defined (i.e., **--short** must be specified) but it cannot be used when parsing arguments using this option specification.
**-o** or **--optional-val**
The option being defined can take a value, but it is optional rather than required. If the option is seen more than once when parsing arguments, only the last value seen is saved. This means the resulting flag variable created by ``argparse`` will zero elements if no value was given with the option else it will have exactly one element.
@@ -33,15 +33,8 @@ The following ``argparse`` options are available:
**-r** or **--required-val**
The option being defined requires a value. If the option is seen more than once when parsing arguments, only the last value seen is saved. This means the resulting flag variable created by ``argparse`` will have exactly one element.
**-m** or **--multiple-vals**
The value of each instance of the option is accumulated. If **--optional-val** is provided, the value is optional, and an empty string is stored if no value is provided. Otherwise, the **--requiured-val** option is implied and each instance of the option requires a value. This means the resulting flag variable created by ``argparse`` will have one element for each instance of this option in the arguments, even for instances that did not provide a value.
**-d** or **--delete**
The option and any values will be deleted from the ``$argv_opts`` variables set by ``argparse``
(as with other options, it will also be deleted from ``$argv``).
**-v** or **--validate** *COMMAND* *OPTION...*
This option must be the last one, and requires one of ``-o``, ``-r``, or ``-m``. All the remaining arguments are interpreted a fish script to run to validate the value of the argument, see ``argparse`` documentation for more details. Note that the interpretation of *COMMAND* *OPTION...* is similar to ``eval``, so you may need to quote or escape special characters *twice* if you want them to be interpreted literally when the validate script is run.
**--multiple-vals**
The option being defined requires a value each time it is seen. Each instance is stored. This means the resulting flag variable created by ``argparse`` will have one element for each instance of this option in the arguments.
**-h** or **--help**
Displays help about using this command.
@@ -66,25 +59,18 @@ Same as above but with a second flag that requires a value:
::
set -l options (fish_opt -s h -l help)
set options $options (fish_opt -s m -l max -r)
set options $options (fish_opt -s m -l max --required-val)
argparse $options -- $argv
Same as above but the value of the second flag cannot be the empty string:
::
set -l options (fish_opt -s h -l help)
set options $options (fish_opt -s m -l max -rv test \$_flag_valu != "''")
argparse $options -- $argv
Same as above but with a third flag that can be given multiple times saving the value of each instance seen and only a long flag name (``--token``) is defined:
Same as above but with a third flag that can be given multiple times saving the value of each instance seen and only the long flag name (``--token``) can be used:
::
set -l options (fish_opt --short=h --long=help)
set options $options (fish_opt --short=m --long=max --required-val --validate test \$_flag_valu != "''")
set options $options (fish_opt --long=token --multiple-vals)
set options $options (fish_opt --short=m --long=max --required-val)
set options $options (fish_opt --short=t --long=token --multiple-vals --long-only)
argparse $options -- $argv

View File

@@ -48,7 +48,7 @@ This refuses to store any immediate "vault", "mysql" or "ls" calls. Commands sta
function fish_should_add_to_history
# I don't want `git pull`s in my history when I'm in a specific repository
if string match -qr '^git pull' -- "$argv"
if string match -qr '^git pull'
and string match -qr "^/home/me/my-secret-project/" -- (pwd -P)
return 1
end

View File

@@ -21,7 +21,7 @@ A function is a list of commands that will be executed when the name of the func
The following options are available:
**-a** *NAMES* or **--argument-names** *NAMES*
Assigns the value of successive command-line arguments to the names given in *NAMES* (separated by spaces). These are the same arguments given in :envvar:`argv`, and are still available there (unless ``--inherit-variable argv`` was used or one of the given *NAMES* is ``argv``). See also :ref:`Argument Handling <variables-argv>`.
Has to be the last option. Assigns the value of successive command-line arguments to the names given in *NAMES* (separated by space). These are the same arguments given in :envvar:`argv`, and are still available there. See also :ref:`Argument Handling <variables-argv>`.
**-d** *DESCRIPTION* or **--description** *DESCRIPTION*
A description of what the function does, suitable as a completion description.
@@ -40,7 +40,7 @@ The following options are available:
Run this function when the variable *VARIABLE_NAME* changes value. Note that :program:`fish` makes no guarantees on any particular timing or even that the function will be run for every single ``set``. Rather it will be run when the variable has been set at least once, possibly skipping some values or being run when the variable has been set to the same value (except for universal variables set in other shells - only changes in the value will be picked up for those).
**-j** *PID* or **--on-job-exit** *PID*
Run this function when the job containing a child process with the given process ID *PID* exits. Instead of a PID, the string 'caller' can be specified. This is only allowed when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
Run this function when the job containing a child process with the given process identifier *PID* exits. Instead of a PID, the string 'caller' can be specified. This is only allowed when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
This will not trigger for :doc:`disowned <disown>` jobs.
**-p** *PID* or **--on-process-exit** *PID*

View File

@@ -13,7 +13,7 @@ Synopsis
path extension GENERAL_OPTIONS [PATH ...]
path filter GENERAL_OPTIONS [-v | --invert]
[-d] [-f] [-l] [-r] [-w] [-x]
[(-t | --type) TYPE] [(-p | --perm) PERMISSION] [--all] [PATH ...]
[(-t | --type) TYPE] [(-p | --perm) PERMISSION] [PATH ...]
path is GENERAL_OPTIONS [(-v | --invert)] [(-t | --type) TYPE]
[-d] [-f] [-l] [-r] [-w] [-x]
[(-p | --perm) PERMISSION] [PATH ...]
@@ -22,7 +22,7 @@ Synopsis
path resolve GENERAL_OPTIONS [PATH ...]
path change-extension GENERAL_OPTIONS EXTENSION [PATH ...]
path sort GENERAL_OPTIONS [-r | --reverse]
[-u | --unique] [--key=(basename | dirname | path)] [PATH ...]
[-u | --unique] [--key=basename|dirname|path] [PATH ...]
GENERAL_OPTIONS
[-z | --null-in] [-Z | --null-out] [-q | --quiet]
@@ -148,7 +148,7 @@ Examples
> echo $path$extension
# reconstructs the original path again.
./foo.mp4
.. _cmd-path-filter:
"filter" subcommand
@@ -158,7 +158,7 @@ Examples
path filter [-z | --null-in] [-Z | --null-out] [-q | --quiet] \
[-d] [-f] [-l] [-r] [-w] [-x] \
[-v | --invert] [(-t | --type) TYPE] [(-p | --perm) PERMISSION] [--all] [PATH ...]
[-v | --invert] [(-t | --type) TYPE] [(-p | --perm) PERMISSION] [PATH ...]
``path filter`` returns all of the given paths that match the given checks. In all cases, the paths need to exist, nonexistent paths are always filtered.
@@ -180,10 +180,6 @@ When a path starts with ``-``, ``path filter`` will prepend ``./`` to avoid it b
It returns 0 if at least one path passed the filter.
With ``--all``, return status 0 (true) if all paths pass the filter, and status 1 (false) if any path fails. This is equivalent to ``not path filter -v``. It produces no output, only a status.
When ``--all`` combined with ``--invert``, it returns status 0 (true) if all paths fail the filter and status 1 (false) if any path passes.
``path is`` is shorthand for ``path filter -q``, i.e. just checking without producing output, see :ref:`The is subcommand <cmd-path-is>`.
Examples
@@ -215,9 +211,6 @@ Examples
>_ path filter -fx $PATH/*
# Prints all possible commands - the first entry of each name is what fish would execute!
>_ path filter --all /usr/bin /usr/argagagji
# This returns 1 (false) because not all paths pass the filter.
.. _cmd-path-is:
"is" subcommand

View File

@@ -55,9 +55,3 @@ Examples
>_ prompt_pwd --full-length-dirs=2 --dir-length=1
/t/b/s/with/mustard
>_ echo (prompt_pwd | string split /)[-1]
mustard
>_ echo (string join / (prompt_pwd | string split /)[-3..-1])
s/with/mustard

View File

@@ -83,12 +83,8 @@ The following options control how much is read and how it is stored:
**-n** or **--nchars** *NCHARS*
Makes ``read`` return after reading *NCHARS* characters or the end of the line, whichever comes first.
**-t**, **--tokenize** or **--tokenize-raw**
Causes read to split the input into variables by the shell's tokenization rules.
This means it will honor quotes and escaping.
This option is of course incompatible with other options to control splitting like **--delimiter** and does not honor :envvar:`IFS` (like fish's tokenizer).
The **-t** -or **--tokenize** variants perform quote removal, so e.g. ``a\ b`` is stored as ``a b``.
However variables and command substitutions are not expanded.
**-t** -or **--tokenize**
Causes read to split the input into variables by the shell's tokenization rules. This means it will honor quotes and escaping. This option is of course incompatible with other options to control splitting like **--delimiter** and does not honor :envvar:`IFS` (like fish's tokenizer). It saves the tokens in the manner they'd be passed to commands on the commandline, so e.g. ``a\ b`` is stored as ``a b``. Note that currently it leaves command substitutions intact along with the parentheses.
**-a** or **--list**
Stores the result as a list in a single variable. This option is also available as **--array** for backwards compatibility.

View File

@@ -30,11 +30,9 @@ Synopsis
status job-control CONTROL_TYPE
status features
status test-feature FEATURE
status build-info
status buildinfo
status get-file FILE
status list-files [PATH]
status terminal
status test-terminal-feature FEATURE
Description
-----------
@@ -101,49 +99,27 @@ The following operations (subcommands) are available:
Sets the job control type to *CONTROL_TYPE*, which can be **none**, **full**, or **interactive**.
**features**
Lists all available :ref:`feature flags <featureflags>`.
Lists all available feature flags.
**test-feature** *FEATURE*
Returns 0 when FEATURE is enabled, 1 if it is disabled, and 2 if it is not recognized.
**build-info**
**buildinfo**
This prints information on how fish was build - which architecture, which build system or profile was used, etc.
This is mainly useful for debugging.
.. _status-get-file:
**get-file** *FILE*
NOTE: this subcommand is mainly intended for fish's internal use; let us know if you want to use it elsewhere.
This prints a file embedded in the fish binary at compile time. This includes the default set of functions and completions,
as well as the man pages and themes. Which files are included depends on build settings.
Returns 0 if the file was included, 1 otherwise.
**list-files** *FILE*
NOTE: this subcommand is mainly intended for fish's internal use; let us know if you want to use it elsewhere.
This lists the files embedded in the fish binary at compile time. Only files where the path starts with the optional *FILE* argument are shown.
Returns 0 if something was printed, 1 otherwise.
.. _status-terminal:
**terminal**
Prints the name and version of the terminal fish is running inside (for example as reported via :ref:`XTVERSION <term-compat-xtversion>`).
This is not available during early startup but only starting from when the first interactive prompt is shown, possibly via builtin :doc:`read <read>`,
so before the first ``fish_prompt`` or ``fish_read`` :ref:`event <event>`.
.. _status-test-terminal-features:
**test-terminal-feature** *FEATURE*
Returns 0 when the terminal was :ref:`detected <term-compat-xtgettcap>` to support the given feature.
Like :ref:`status terminal <status-terminal>`, this only works once the first interactive prompt is shown.
Currently the only available *FEATURE* is :ref:`scroll-content-up <term-compat-indn>`.
An error will be printed when passed an unrecognized feature.
Notes
-----
For backwards compatibility most subcommands can also be specified as a long or short option. For example, rather than ``status is-login`` you can type ``status --is-login``. The flag forms are deprecated and may be removed in a future release.
For backwards compatibility most subcommands can also be specified as a long or short option. For example, rather than ``status is-login`` you can type ``status --is-login``. The flag forms are deprecated and may be removed in a future release (but not before fish 4.0).
You can only specify one subcommand per invocation even if you use the flag form of the subcommand.

View File

@@ -8,7 +8,7 @@ Synopsis
.. synopsis::
string pad [-r | --right] [-C | --center] [(-c | --char) CHAR] [(-w | --width) INTEGER]
string pad [-r | --right] [(-c | --char) CHAR] [(-w | --width) INTEGER]
[STRING ...]
.. END SYNOPSIS
@@ -22,8 +22,6 @@ Description
The escape sequences reflect what fish knows about, and how it computes its output. Your terminal might support more escapes, or not support escape sequences that fish knows about.
If **-C** or **--center** is given, add the padding to before and after the string. If it is impossible to perfectly center the result (because the required amount of padding is an odd number), extra padding will be added to the left, unless **--right** is also given.
If **-r** or **--right** is given, add the padding after a string.
If **-c** or **--char** is given, pad with *CHAR* instead of whitespace.

View File

@@ -8,9 +8,9 @@ Synopsis
.. synopsis::
string split [(-f | --fields) FIELDS [-a | --allow-empty]] [(-m | --max) MAX] [-n | --no-empty]
string split [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
[-q | --quiet] [-r | --right] SEP [STRING ...]
string split0 [(-f | --fields) FIELDS [-a | --allow-empty]] [(-m | --max) MAX] [-n | --no-empty]
string split0 [(-f | --fields) FIELDS] [(-m | --max) MAX] [-n | --no-empty]
[-q | --quiet] [-r | --right] [STRING ...]
.. END SYNOPSIS
@@ -20,7 +20,7 @@ Description
.. BEGIN DESCRIPTION
``string split`` splits each *STRING* on the separator *SEP*, which can be an empty string. If **-m** or **--max** is specified, at most MAX splits are done on each *STRING*. If **-r** or **--right** is given, splitting is performed right-to-left. This is only useful in combination with **-m** or **--max**. With **-n** or **--no-empty**, empty results are excluded from consideration (e.g. ``hello\n\nworld`` would expand to two strings and not three). Exit status: 0 if at least one split was performed, or 1 otherwise.
``string split`` splits each *STRING* on the separator *SEP*, which can be an empty string. If **-m** or **--max** is specified, at most MAX splits are done on each *STRING*. If **-r** or **--right** is given, splitting is performed right-to-left. This is useful in combination with **-m** or **--max**. With **-n** or **--no-empty**, empty results are excluded from consideration (e.g. ``hello\n\nworld`` would expand to two strings and not three). Exit status: 0 if at least one split was performed, or 1 otherwise.
Use **-f** or **--fields** to print out specific fields. FIELDS is a comma-separated string of field numbers and/or spans. Each field is one-indexed, and will be printed on separate lines. If a given field does not exist, then the command exits with status 1 and does not print anything, unless **--allow-empty** is used.

View File

@@ -18,7 +18,7 @@ Synopsis
[-g | --groups-only] [-r | --regex] [-n | --index]
[-q | --quiet] [-v | --invert]
PATTERN [STRING ...]
string pad [-r | --right] [-C | --center] [(-c | --char) CHAR] [(-w | --width) INTEGER]
string pad [-r | --right] [(-c | --char) CHAR] [(-w | --width) INTEGER]
[STRING ...]
string repeat [(-n | --count) COUNT] [(-m | --max) MAX] [-N | --no-newline]
[-q | --quiet] [STRING ...]

View File

@@ -82,7 +82,7 @@ As a more comprehensive example, here's a commented excerpt of the completions f
#
# The `-n`/`--condition` option takes script as a string, which it executes.
# If it returns true, the completion is offered.
# Here the condition is the `__fish_seen_subcommand_from` helper function.
# Here the condition is the `__fish_seen_subcommands_from` helper function.
# It returns true if any of the given commands is used on the commandline,
# as determined by a simple heuristic.
# For more complex uses, you can write your own function.

View File

@@ -10,19 +10,9 @@ import glob
import os.path
import subprocess
import sys
from sphinx.highlighting import lexers
from sphinx.errors import SphinxWarning
from docutils import nodes
try:
import sphinx_markdown_builder
extensions = [
"sphinx_markdown_builder",
]
except ImportError:
pass
# -- Helper functions --------------------------------------------------------
@@ -45,14 +35,11 @@ def issue_role(name, rawtext, text, lineno, inliner, options=None, content=None)
return [link], []
def remove_fish_indent_lexer(app):
if app.builder.name in ("man", "markdown"):
del lexers["fish-docs-samples"]
# -- Load our extensions -------------------------------------------------
def setup(app):
# Our own pygments lexers
from sphinx.highlighting import lexers
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, this_dir)
from fish_indent_lexer import FishIndentLexer
@@ -65,8 +52,6 @@ def setup(app):
app.add_config_value("issue_url", default=None, rebuild="html")
app.add_role("issue", issue_role)
app.connect("builder-inited", remove_fish_indent_lexer)
# The default language to assume
highlight_language = "fish-docs-samples"
@@ -87,7 +72,7 @@ elif "FISH_BUILD_VERSION" in os.environ:
ret = os.environ["FISH_BUILD_VERSION"]
else:
ret = subprocess.check_output(
("../build_tools/git_version_gen.sh", "--stdout"), stderr=subprocess.STDOUT
("fish_indent", "--version"), stderr=subprocess.STDOUT
).decode("utf-8")
# The full version, including alpha/beta/rc tags

View File

@@ -332,8 +332,7 @@ Some bindings are common across Emacs and vi mode, because they aren't text edit
- :kbd:`ctrl-u` removes contents from the beginning of line to the cursor (moving it to the :ref:`killring <killring>`).
- :kbd:`ctrl-l` pushes any text above the prompt to the terminal's scrollback,
then clears and repaints the screen.
- :kbd:`ctrl-l` clears and repaints the screen.
- :kbd:`ctrl-w` removes the previous path component (everything up to the previous "/", ":" or "@") (moving it to the :ref:`killring`).
@@ -363,7 +362,7 @@ Some bindings are common across Emacs and vi mode, because they aren't text edit
- :kbd:`alt-v` Same as :kbd:`alt-e`.
- :kbd:`alt-s` Prepends ``sudo`` to the current commandline. If the commandline is empty, prepend ``sudo`` to the last commandline. If ``sudo`` is not installed, various similar commands are tried: ``doas``, ``please``, and ``run0``.
- :kbd:`alt-s` Prepends ``sudo`` to the current commandline. If the commandline is empty, prepend ``sudo`` to the last commandline.
- :kbd:`ctrl-space` Inserts a space without expanding an :ref:`abbreviation <abbreviations>`. For vi mode, this only applies to insert-mode.
@@ -508,13 +507,7 @@ Command mode is also known as normal mode.
- :kbd:`backspace` moves the cursor left.
- :kbd:`g,g` / :kbd:`G` moves the cursor to the beginning/end of the commandline, respectively.
- :kbd:`~` toggles the case (upper/lower) of the character and moves to the next character.
- :kbd:`g,u` lowercases to the end of the word.
- :kbd:`g,U` uppercases to the end of the word.
- :kbd:`g` / :kbd:`G` moves the cursor to the beginning/end of the commandline, respectively.
- :kbd:`:,q` exits fish.
@@ -558,10 +551,6 @@ Visual mode
- :kbd:`~` toggles the case (upper/lower) on the selection, and enters :ref:`command mode <vi-mode-command>`.
- :kbd:`g,u` lowercases the selection, and enters :ref:`command mode <vi-mode-command>`.
- :kbd:`g,U` uppercases the selection, and enters :ref:`command mode <vi-mode-command>`.
- :kbd:`",*,y` copies the selection to the clipboard, and enters :ref:`command mode <vi-mode-command>`.
.. _custom-binds:

View File

@@ -181,7 +181,7 @@ The destination of a stream can be changed using something called *redirection*.
- An ampersand (``&``) followed by the number of another file descriptor like ``&2`` for standard error. The output will be written to the destination descriptor.
- An ampersand followed by a minus sign (``&-``). The file descriptor will be closed. Note: This may cause the program to fail because its writes will be unsuccessful.
As a convenience, the redirection ``&>`` can be used to direct both stdout and stderr to the same destination. For example, ``echo hello &> all_output.txt`` redirects both stdout and stderr to the file ``all_output.txt``. This is equivalent to ``echo hello > all_output.txt 2>&1``. You can also use ``&>>`` to append both stdout and stderr to the same destination.
As a convenience, the redirection ``&>`` can be used to direct both stdout and stderr to the same destination. For example, ``echo hello &> all_output.txt`` redirects both stdout and stderr to the file ``all_output.txt``. This is equivalent to ``echo hello > all_output.txt 2>&1``.
Any arbitrary file descriptor can be used in a redirection by prefixing the redirection with the FD number.
@@ -1519,7 +1519,7 @@ For more information on argparse, like how to handle option arguments, see :doc:
PATH variables
^^^^^^^^^^^^^^
Path variables are a special kind of variable used to support colon-delimited path lists including :envvar:`PATH`, :envvar:`CDPATH`, :envvar:`MANPATH`, :envvar:`PYTHONPATH`, :envvar:`LANGUAGE` (for :doc:`localization <cmds/_>`) etc. All variables that end in "PATH" (case-sensitive) become PATH variables by default.
Path variables are a special kind of variable used to support colon-delimited path lists including :envvar:`PATH`, :envvar:`CDPATH`, :envvar:`MANPATH`, :envvar:`PYTHONPATH`, etc. All variables that end in "PATH" (case-sensitive) become PATH variables by default.
PATH variables act as normal lists, except they are implicitly joined and split on colons.
@@ -1572,13 +1572,11 @@ You can change the settings of fish by changing the values of certain variables.
.. envvar:: fish_term24bit
If this is set to 0, fish will not output 24-bit RGB true-color sequences but the nearest color on the 256 color palette (or the 16 color palette, if :envvar:`fish_term256` is 0).
See also :doc:`set_color <cmds/set_color>`.
The default is 1 but for historical reasons, fish defaults to behaving as if it was 0 on some terminals that are known to not support true-color sequences.
.. envvar:: fish_term256
If this is set to 0 and :envvar:`fish_term24bit` is 0, translate RGB colors down to the 16 color palette.
Also, if this is set to 0, :doc:`set_color <cmds/set_color>` commands such as ``set_color ff0000 red`` will prefer the named color.
Also, if this is set to 0, :doc:`set_color <cmds/set_color>`/` commands such as ``set_color ff0000 red`` will prefer the named color.
.. envvar:: fish_ambiguous_width
@@ -1671,10 +1669,6 @@ Fish also provides additional information through the values of certain environm
a list of arguments to the shell or function. ``argv`` is only defined when inside a function call, or if fish was invoked with a list of arguments, like ``fish myscript.fish foo bar``. This variable can be changed.
.. envvar:: argv_opts
:doc:`argparse <cmds/argparse>` sets this to the list of successfully parsed options, including option-arguments. This variable can be changed.
.. envvar:: CMD_DURATION
the runtime of the last command in milliseconds.
@@ -2032,9 +2026,7 @@ You can see the current list of features via ``status features``::
ampersand-nobg-in-token on 3.4 & only backgrounds if followed by a separating character
remove-percent-self off 4.0 %self is no longer expanded (use $fish_pid)
test-require-arg off 4.0 builtin test requires an argument
mark-prompt on 4.0 write OSC 133 prompt markers to the terminal
ignore-terminfo on 4.1 do not look up $TERM in terminfo database
query-term on 4.1 query the TTY to enable extra functionality
Here is what they mean:
@@ -2044,11 +2036,7 @@ Here is what they mean:
- ``ampersand-nobg-in-token`` was introduced in fish 3.4 (and made the default in 3.5). It makes it so a ``&`` i no longer interpreted as the backgrounding operator in the middle of a token, so dealing with URLs becomes easier. Either put spaces or a semicolon after the ``&``. This is recommended formatting anyway, and ``fish_indent`` will have done it for you already.
- ``remove-percent-self`` turns off the special ``%self`` expansion. It was introduced in 4.0. To get fish's pid, you can use the :envvar:`fish_pid` variable.
- ``test-require-arg`` removes :doc:`builtin test <cmds/test>`'s one-argument form (``test "string"``. It was introduced in 4.0. To test if a string is non-empty, use ``test -n "string"``. If disabled, any call to ``test`` that would change sends a :ref:`debug message <debugging-fish>` of category "deprecated-test", so starting fish with ``fish --debug=deprecated-test`` can be used to find offending calls.
- ``mark-prompt`` makes fish report to the terminal the beginning and and of both shell prompts and command output.
- ``ignore-terminfo`` disables lookup of $TERM in the terminfo database. Use ``no-ignore-terminfo`` to turn it back on.
- ``query-term`` allows fish to query the terminal by writing escape sequences and reading the terminal's response.
This enables features such as :ref:`scrolling <term-compat-cursor-position-report>`.
If you use an incompatible terminal, you can -- for the time being -- work around it by running (once) ``set -Ua fish_features no-query-term``.
These changes are introduced off by default. They can be enabled on a per session basis::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -5,7 +5,7 @@ fish writes various control sequences to the terminal.
Some must be implemented to enable basic functionality,
while others enable optional features and may be ignored by the terminal.
The terminal must be able to parse Control Sequence Introducer (CSI) commands, Operating System Commands (OSC) and :ref:`optionally <term-compat-dcs-gnu-screen>` Device Control Strings (DCS).
The terminal must be able to parse Control Sequence Introducer (CSI) commands, Operating System Commands (OSC) and optionally Device Control Strings (DCS).
These are defined by ECMA-48.
If a valid CSI, OSC or DCS sequence does not represent a command implemented by the terminal, the terminal must ignore it.
@@ -68,15 +68,10 @@ Required Commands
- clear
- Clear the screen.
- VT100
* - .. _term-compat-primary-da:
``\e[0c``
* - ``\e[0c``
-
- Request Primary Device Attribute.
- Request primary device attribute.
The terminal must respond with a CSI command that starts with the ``?`` parameter byte (so a sequence starting with ``\e[?``) and has ``c`` as final byte.
Failure to implement this will cause a brief pause at startup followed by a warning.
For the time being, both can be turned off by turning off the ``query-terminal`` :ref:`feature flag <featureflags>`.
- VT100
* - n/a
- am
@@ -194,30 +189,17 @@ Optional Commands
- Su
- Reset underline color to the default (follow the foreground color).
- kitty
* - .. _term-compat-indn:
``\e[ Ps S``
* - ``\e[ Ps S``
- indn
- Scroll up the content (not the viewport) Ps lines (called ``SCROLL UP`` / ``SU`` by ECMA-48 and "scroll forward" by terminfo).
When fish detects support for this feature, :ref:`status test-terminal-features scroll-content-up <status-test-terminal-features>` will return 0,
which enables the :kbd:`ctrl-l` binding to use the :ref:`scrollback-push <special-input-functions-scrollback-push>` special input function.
- ECMA-48
- Scroll forward Ps lines.
-
* - ``\e[= Ps u``, ``\e[? Ps u``
- n/a
- Enable the kitty keyboard protocol.
- kitty
* - .. _term-compat-cursor-position-report:
``\e[6n``
* - ``\e[6n``
- n/a
- Request cursor position report.
The response must be of the form ``\e[ Ps ; Ps R``
where the first parameter is the row number
and the second parameter is the column number.
Both start at 1.
This is used by the :ref:`scrollback-push <special-input-functions-scrollback-push>` special input function,
and inside terminals that implement the OSC 133 :ref:`click_events <term-compat-osc-133>` feature.
- VT100
* - ``\e[ \x20 q``
- Se
@@ -228,12 +210,9 @@ Optional Commands
- Ss
- Set cursor style (DECSCUSR); Ps is 2, 4 or 6 for block, underscore or line shape.
- VT520
* - .. _term-compat-xtversion:
``\e[ Ps q``
* - ``\e[ Ps q``
- n/a
- Request terminal name and version (XTVERSION).
This is only used for temporary workarounds for incompatible terminals.
- XTerm
* - ``\e[?25h``
- cvvis
@@ -282,19 +261,15 @@ Optional Commands
- Create a `hyperlink (OSC 8) <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`_.
This is used in fish's man pages.
- GNOME Terminal
* - .. _term-compat-osc-52:
``\e]52;c; Pt \x07``
* - ``\e]52;c; Pt \x07``
-
- Copy to clipboard (OSC 52). Used by :doc:`fish_clipboard_copy <cmds/fish_clipboard_copy>`.
- Copy to clipboard (OSC 52).
- XTerm
* - .. _term-compat-osc-133:
* - .. _click-events:
``\e]133;A; click_events=1\x07``
-
- Mark prompt start (OSC 133), with kitty's ``click_events`` extension.
The ``click_events`` extension enables mouse clicks to move the cursor or select pager items,
assuming that :ref:`cursor position reporting <term-compat-cursor-position-report>` is available.
- FinalTerm, kitty
* - ``\e]133;C; cmdline_url= Pt \x07``
-
@@ -304,41 +279,8 @@ Optional Commands
-
- Mark command end (OSC 133); Ps is the exit status.
- FinalTerm
* - .. _term-compat-xtgettcap:
``\eP+q Pt \e\\``
* - ``\eP+q Pt \e\\``
-
- Request terminfo capability (XTGETTCAP).
The parameter is the capability's hex-encoded terminfo code.
To advertise a capability, the response must be of the form
``\eP1+q Pt \e\\`` or ``\eP1+q Pt = Pt \e\\``.
In either variant the first parameter must be the hex-encoded terminfo code.
The second variant's second parameter is ignored.
Currently, fish only queries the :ref:`indn <term-compat-indn>` string capability.
- XTerm (but without string capabilities), kitty;
also adopted by foot, wezterm, contour, ghostty
.. _term-compat-dcs-gnu-screen:
DCS commands and GNU screen
---------------------------
Fully-correct DCS parsing is optional because fish switches to the alternate screen before printing any DCS commands.
However, since GNU screen neither allows turning on the alternate screen buffer by default,
nor treats DCS commands in a compatible way,
fish's initial prompt may be garbled by a DCS payload like ``+q696e646e``.
For the time being, fish works around this by checking for presence of the :envvar:`STY` environment variable.
If that doesn't work for some reason, you can add this to your ``~/.screenrc``:
.. code-block:: none
altscreen on
Or add this to your ``config.fish``::
function GNU-screen-workaround --on-event fish_prompt
commandline -f repaint
functions --erase GNU-screen-workaround
end
- Request terminfo capability (XTGETTCAP). The parameter is the capability's hex-encoded terminfo code.
Specifically, fish asks for the ``indn`` string capability. At the time of writing string capabilities are supported by kitty and foot.
- XTerm, kitty, foot

View File

@@ -1,30 +1,26 @@
FROM alpine:3.19
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV PIP_ROOT_USER_ACTION=ignore
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
RUN apk add --no-cache \
cmake ninja \
bash \
cargo \
cmake \
g++ \
gettext-dev \
git \
libintl \
musl-dev \
ninja \
pcre2-dev \
py3-pexpect \
py3-pip \
python3 \
rust \
rustfmt \
sudo \
tmux
RUN pip install --break-system-packages black
RUN addgroup -g 1000 fishuser
RUN adduser \
@@ -44,6 +40,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -8,13 +8,17 @@ RUN cd /etc/yum.repos.d/ && \
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
# install powertools to get ninja-build
RUN dnf -y install dnf-plugins-core \
&& dnf config-manager --set-enabled powertools \
&& yum install --assumeyes epel-release \
&& yum install --assumeyes \
cargo \
cmake \
diffutils \
gcc-c++ \
git \
ninja-build \
python3 \
python3-pexpect \
openssl \
@@ -35,6 +39,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -5,34 +5,22 @@ set -e
# This script is copied into the root directory of our Docker tests.
# It is the entry point for running Docker-based tests.
echo build_tools/check.sh >>~/.bash_history
cd /fish-source
cd ~/fish-build
git config --global --add safe.directory /fish-source
export CARGO_TARGET_DIR="$HOME"/fish-build
interactive_shell() {
echo
echo "+ export=CARGO_TARGET_DIR=$CARGO_TARGET_DIR"
echo
bash -i
}
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug /fish-source "$@"
# Spawn a shell if FISH_RUN_SHELL_BEFORE_TESTS is set.
if test -n "$FISH_RUN_SHELL_BEFORE_TESTS"
then
interactive_shell
bash -i
fi
set +e
build_tools/check.sh
(set +e; ninja && ninja fish_run_tests)
RES=$?
set -e
# Drop the user into a shell if FISH_RUN_SHELL_AFTER_TESTS is set.
if test -n "$FISH_RUN_SHELL_AFTER_TESTS"; then
interactive_shell
bash -i
fi
exit $RES

View File

@@ -4,9 +4,8 @@ usage() {
cat << EOF
Usage: $(basename "$0") [--shell-before] [--shell-after] DOCKERFILE
Options:
--shell-before Before the tests start, run a bash shell
--shell-after After the tests end, run a bash shell
--lint, --no-lint Enable/disable linting and failure on warnings
--shell-before Before the tests start, run a bash shell
--shell-after After the tests end, run a bash shell
EOF
exit 1
}
@@ -30,12 +29,6 @@ while [ $# -gt 1 ]; do
--shell-after)
DOCKER_EXTRA_ARGS="$DOCKER_EXTRA_ARGS --env FISH_RUN_SHELL_AFTER_TESTS=1"
;;
--lint)
DOCKER_EXTRA_ARGS="$DOCKER_EXTRA_ARGS --env FISH_CHECK_LINT=true"
;;
--no-lint)
DOCKER_EXTRA_ARGS="$DOCKER_EXTRA_ARGS --env FISH_CHECK_LINT=false"
;;
*)
usage
;;

View File

@@ -2,9 +2,11 @@ FROM fedora:latest
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
RUN dnf install --assumeyes \
cmake \
diffutils \
gcc-c++ \
git-core \
ninja-build \
pcre2-devel \
python3 \
python3-pip \
@@ -26,6 +28,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -1,20 +1,20 @@
FROM ubuntu:20.04
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV CFLAGS="-m32"
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
curl \
cmake \
g++-multilib \
gettext \
git \
locales \
openssl \
ninja-build \
pkg-config \
python3 \
python3-pexpect \
@@ -38,13 +38,6 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh \
COPY fish_run_tests.sh /
ENV \
CFLAGS=-m32 \
PCRE2_SYS_STATIC=1 \
FISH_CHECK_TARGET_TRIPLE=i686-unknown-linux-gnu
ENV FISH_CHECK_LINT=false
CMD . ~/.cargo/env \
&& rustup target add ${FISH_CHECK_TARGET_TRIPLE} \
&& /fish_run_tests.sh
&& rustup target add i686-unknown-linux-gnu \
&& /fish_run_tests.sh -DFISH_USE_SYSTEM_PCRE2=OFF -DRust_CARGO_TARGET=i686-unknown-linux-gnu

View File

@@ -1,22 +1,21 @@
FROM arm64v8/ubuntu:focal
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
cmake \
clang \
gettext \
git \
libpcre2-dev \
locales \
openssl \
ninja-build \
python3 \
python3-pexpect \
rustc \
@@ -37,6 +36,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -1,19 +1,19 @@
FROM ubuntu:20.04
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
cmake \
gettext \
git \
locales \
openssl \
ninja-build \
pkg-config \
python3 \
python3-pexpect \
@@ -35,6 +35,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -1,27 +1,26 @@
FROM arm32v7/ubuntu:jammy
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
cmake \
file \
g++ \
gettext \
git \
libpcre2-dev \
locales \
openssl \
ninja-build \
pkg-config \
python3 \
python3-pexpect \
rustc \
rust \
sudo \
tmux \
&& locale-gen en_US.UTF-8 \
@@ -39,6 +38,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -1,20 +1,19 @@
FROM ubuntu:jammy
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
cmake \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
ninja-build \
python3 \
python3-pexpect \
sudo \
@@ -43,17 +42,12 @@ COPY fish_run_tests.sh /
ENV \
RUSTFLAGS=-Zsanitizer=address \
RUSTDOCFLAGS=-Zsanitizer=address \
FISH_CHECK_CARGO_ARGS='-Zbuild-std --features=tsan' \
FISH_CHECK_TARGET_TRIPLE=x86_64-unknown-linux-gnu \
FISH_CI_SAN=1 \
FISH_TEST_MAX_CONCURRENCY=4 \
CC=clang \
CXX=clang++ \
ASAN_OPTIONS=check_initialization_order=1:detect_stack_use_after_return=1:detect_leaks=1 \
LSAN_OPTIONS=verbosity=0:log_threads=0:use_tls=1:print_suppressions=0:suppressions=/fish-source/build_tools/lsan_suppressions.txt
ENV FISH_CHECK_LINT=false
LSAN_OPTIONS=verbosity=0:log_threads=0:use_tls=1:print_suppressions=0:suppressions=/fish-source/build_tools/lsan_suppressions.txt \
FISH_CI_SAN=1
CMD . ~/.cargo/env \
&& ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-$(cat /.llvm-version) \
&& /fish_run_tests.sh
&& ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-$(cat /.llvm-version) \
/fish_run_tests.sh -DASAN=1 -DRust_CARGO_TARGET=x86_64-unknown-linux-gnu

View File

@@ -1,20 +1,19 @@
FROM ubuntu:jammy
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
&& apt-get -y install \
build-essential \
ca-certificates \
cmake \
clang \
curl \
gettext \
git \
libpcre2-dev \
locales \
openssl \
ninja-build \
python3 \
python3-pexpect \
sudo \
@@ -40,11 +39,7 @@ COPY fish_run_tests.sh /
ENV \
RUSTFLAGS=-Zsanitizer=thread \
RUSTDOCFLAGS=-Zsanitizer=thread \
FISH_CHECK_CARGO_ARGS='-Zbuild-std --features=tsan' \
FISH_CHECK_TARGET_TRIPLE=x86_64-unknown-linux-gnu \
FISH_CI_SAN=1 \
FISH_TEST_MAX_CONCURRENCY=4
FISH_CI_SAN=1
ENV FISH_CHECK_LINT=false
CMD . ~/.cargo/env && /fish_run_tests.sh
CMD . ~/.cargo/env \
&& /fish_run_tests.sh -DTSAN=1 -DRust_CARGO_TARGET=x86_64-unknown-linux-gnu

View File

@@ -1,32 +1,28 @@
FROM ubuntu:jammy
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
cmake ninja-build \
&& apt-get -y install \
build-essential \
ca-certificates \
cargo \
cmake \
clang \
gettext \
git \
libpcre2-dev \
locales \
openssl \
ninja-build \
python3 \
python3-pexpect \
rustc \
sudo \
tmux \
python3-pip \
&& locale-gen en_US.UTF-8 \
&& apt-get clean
RUN pip install black
RUN groupadd -g 1000 fishuser \
&& useradd -p $(openssl passwd -1 fish) -d /home/fishuser -m -u 1000 -g 1000 fishuser \
&& adduser fishuser sudo \
@@ -39,6 +35,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

View File

@@ -1,20 +1,18 @@
FROM ubuntu:noble
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
RUN apt-get update \
&& apt-get -y install --no-install-recommends \
adduser \
&& apt-get -y install \
build-essential \
ca-certificates \
curl \
cmake \
gettext \
git \
libpcre2-dev \
locales \
openssl \
ninja-build \
python3 \
python3-pexpect \
tmux \
@@ -38,7 +36,5 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh \
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD . ~/.cargo/env \
&& /fish_run_tests.sh

View File

@@ -1,14 +1,16 @@
FROM opensuse/tumbleweed:latest
LABEL org.opencontainers.image.source=https://github.com/fish-shell/fish-shell
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
RUN zypper --non-interactive install \
bash \
cmake \
diffutils \
gcc-c++ \
git-core \
ninja \
pcre2-devel \
python311 \
python311-pip \
@@ -33,6 +35,4 @@ WORKDIR /home/fishuser
COPY fish_run_tests.sh /
ENV FISH_CHECK_LINT=false
CMD /fish_run_tests.sh

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