From 3567df0a828cb41d61492c770d41b77d99f1c9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89douard=20Lopez?= Date: Sat, 13 Sep 2025 18:33:40 +0200 Subject: [PATCH] feat: support build from source Pull code from Fish repo and build locally --- .github/workflows/build-images.yml | 8 +- CONTRIBUTING.md | 132 ++++++++++++++++++++--------- Dockerfile | 55 ++++++++++-- README.md | 5 +- justfile | 66 +++++---------- makefile | 2 +- 6 files changed, 176 insertions(+), 92 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 19216a2..eaf277a 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -26,8 +26,9 @@ jobs: { fish: 3.6.1, alpine: 3.18 }, { fish: 3.6.3, alpine: 3.19 }, { fish: 3.7.1, alpine: 3.21 }, - { fish: 4.0.2, alpine: "edge" }, - # { fish: 3.6.1, alpine: "edge" }, # priorize latest alpine version + { fish: 4.0.2, alpine: 3.22 }, + # New versions built from source + { fish: 4.1.1, alpine: "edge", source: true }, ] fail-fast: false steps: @@ -73,6 +74,9 @@ jobs: file: ./Dockerfile build-contexts: | alpine=docker-image://alpine:${{ matrix.version.alpine }} + build-args: | + FISH_VERSION=${{ matrix.version.fish }} + BUILD_FROM_SOURCE=${{ matrix.version.source == true }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64,linux/arm/v7 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index acb0290..25208ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,72 +1,128 @@ # Contributing Guide -Alpine images already [provide a `fish` package][alpine-fish] from their repository so we leverage that. +This project supports building Fish Docker images in two ways: +1. **Package installation**: Using pre-built Fish packages from Alpine repositories (faster) +2. **Source compilation**: Building Fish from GitHub source archives (supports any released version) -## Requirement +## Requirements -* [just] (a modern `make`-like system). +* [just] (a modern `make`-like system) +* Docker with buildx support -## Build supported versions +## Building Fish Versions -We provide aliases for already supported version: +The `build` recipe takes the following arguments: -```console -❯ just build- -build-3-0-2 build-3-1-2 build-3-2-2 build-3-3-1 build-3-4-1 build-3-5-1 +| Argument | Description | Default | Notes | +| ------------------ | ------------------------------ | --------- | ---------------------------- | +| `FISH_VERSION` | Fish version to build | required | e.g., "4.0.2", "4.1.1" | +| `ALPINE_VERSION` | Alpine base image version | required | e.g., "3.22", "edge" | +| `BUILD_FROM_SOURCE`| Build method | "false" | "true" for source build | +| `verbose` | Build output verbosity | "false" | "verbose" for detailed logs | + +### Build from Alpine Package (Recommended) + +For Fish versions available in Alpine repositories: + +```fish +# Build Fish 4.0.2 from Alpine 3.22 package +just build 4.0.2 3.22 + +# Build from source with verbose output +just build 4.0.2 3.22 false verbose ``` -## Test version +### Build from Source -Check provide version match `fish --version` output: +For newer Fish versions not yet available in Alpine packages: - just test 3.5.1 +```fish +# Build Fish 4.1.1 from source +just build 4.1.1 edge true +# Build with verbose output +just build 4.1.1 edge true verbose +``` -## Add a new `Alpine`/`Fish` +## Testing Versions -When a **new Fish version is available through alpine**, you can use the `build` recipe create a new image: +Verify that the built image contains the expected Fish version: -```console -❯ just build "edge" "3.5.1-r2" +```fish +# Test Fish 4.0.2 built on Alpine 3.22 +just test 4.0.2 3.22 ``` -The `build` recipe takes 3 arguments: +The test command will: -| Argument | Description | Need | -| ---------------- | -------------------------- | ---------------------- | -| `ALPINE_VERSION` | to use as base image | **required** | -| `FISH_VERSION` | to tag the resulting image | _optional_ | -| `verbose` | debug level | _optional_ | +* Show expected vs actual Fish versions with color coding +* Display ✓ VERSION MATCH for successful matches +* Display ✗ VERSION MISMATCH for failures +* Handle missing Docker images gracefully - Required in the CI build process. +## Adding New Fish Versions -## Build Unsupported versions +### For Versions Available in Alpine -This is **not yet supported**, but we are open for PR! :heart: +When Alpine releases a new Fish package, add it to the GitHub Actions matrix in `.github/workflows/build-images.yml`: -### Proposal +```yaml +{ fish: 4.0.7, alpine: "edge" } +``` -:bulb: An idea would be to add a hook in the [Dockerfile]. e.g.: +### For Versions Not Yet in Alpine -```Dockerfile -ARG FISH_GIT_SHA="" -… -RUN [ -n "${FISH_GIT_SHA}" ] && make install-fish-from-source ${FISH_GIT_SHA} +For newer Fish versions, use source builds in the matrix: + +```yaml +{ fish: 4.2.0, alpine: "edge", source: true } ``` -And +The build system will automatically: + +* Download the source archive from GitHub releases +* Compile Fish with all required dependencies (including Rust for newer versions) +* Create a Docker image with the specified Fish version + +### Manual Local Builds + +You can also build any Fish version locally: + +```fish +# Build latest Fish from source +just build 4.1.1 edge true -```make -install-fish-from-source: - git clone --depth 1 @${FISH_FROM_SOURCE_GIT_SHA} - +# Build specific older version from package +just build 3.7.1 3.21 false ``` +## Technical Details + +### Build Methods + +The Dockerfile supports two installation methods controlled by the `BUILD_FROM_SOURCE` argument: + +* **Package method** (`BUILD_FROM_SOURCE=false`): Fast builds using Alpine's pre-built Fish packages +* **Source method** (`BUILD_FROM_SOURCE=true`): Compiles Fish from GitHub release archives + +### Multi-stage Build + +The build process uses Docker multi-stage builds: + +1. **Build stage**: Downloads and compiles Fish from source (when needed) +2. **Final stage**: Creates minimal runtime image with Fish installed + +### Source Build Dependencies + +When building from source, the following dependencies are installed: + +* Standard build tools (build-base, cmake, make) +* Fish-specific libraries (ncurses-dev, pcre2-dev, gettext-dev) +* Rust and Cargo (required for Fish 4.1+ versions) + ## Release See [RELEASE.md]. [just]: https://github.com/casey/just -[alpine-fish]: https://pkgs.alpinelinux.org/packages?name=fish&repo=&arch= -[Dockerfile]: ./Dockerfile -[RELEASE.md]: ./RELEASE.md \ No newline at end of file +[RELEASE.md]: ./RELEASE.md diff --git a/Dockerfile b/Dockerfile index a31e957..bc8fbe9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,39 @@ # to specify the image version, cf. https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context # hadolint ignore=DL3007 # use 'edge' so automated build on docker hub use it instead of latest stable -FROM alpine:latest +FROM alpine:latest as base + +# Build arguments +ARG FISH_VERSION="" +ARG BUILD_FROM_SOURCE="false" + +# Build stage for Fish from source +FROM base as fish-builder +RUN if [ "$BUILD_FROM_SOURCE" = "true" ]; then \ + apk add --no-cache --virtual .build-deps \ + build-base \ + cmake \ + ncurses-dev \ + pcre2-dev \ + gettext-dev \ + wget \ + tar \ + rust \ + cargo && \ + wget -O fish-${FISH_VERSION}.tar.xz "https://github.com/fish-shell/fish-shell/releases/download/${FISH_VERSION}/fish-${FISH_VERSION}.tar.xz" && \ + tar -xf fish-${FISH_VERSION}.tar.xz && \ + cd fish-${FISH_VERSION} && \ + cmake -DCMAKE_INSTALL_PREFIX=/usr/local . && \ + make && \ + make install && \ + apk del .build-deps; \ + fi + +# Final stage +FROM base as with-fish + +# Copy Fish binary if built from source +COPY --from=fish-builder /usr/local /usr/local # Common runtime dependencies of the fish shell # We need bash for devcontainer's feature installation scripts, e.g. https://github.com/devcontainers/features/tree/main/src/git @@ -13,14 +45,25 @@ RUN apk add --no-cache \ make \ git \ bash \ - fish + libgcc \ + ncurses \ + pcre2 \ + gettext && \ + if [ "$BUILD_FROM_SOURCE" != "true" ]; then \ + apk add --no-cache fish; \ + fi + +# Ensure fish is in PATH regardless of installation method +ENV PATH="/usr/local/bin:$PATH" +FROM with-fish AS final WORKDIR /workspace COPY ./makefile /workspace/ -RUN make \ - print-fish-version \ - add-nemo-user \ - move-to-container-user-home +RUN echo "$PATH" +RUN ls /usr/local/ /usr/local/bin/ +RUN make print-fish-version +RUN make add-nemo-user +RUN make move-to-container-user-home # As `nemo` user USER nemo diff --git a/README.md b/README.md index 2728987..5b66c55 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ We provide the following versions of Fish, thanks to Alpine package. Simplify sp | `3.5.1` | `3.17` | | `3.6.1` | `3.18` | | `3.6.3` | `3.19` | -| `3.7.1` | `3.21` | -| `4.0.2` | `edge`, `latest` | +| `3.7.1` | `3.21` | +| `4.0.2` | `3.22` | +| `4.0.6` | `edge`, `latest` | diff --git a/justfile b/justfile index 40632e4..005ba4d 100644 --- a/justfile +++ b/justfile @@ -1,55 +1,35 @@ set shell := ["fish", "-c"] -build ALPINE_VERSION FISH_VERSION verbose="verbose": +# Internal build recipe (prefixed with _ to indicate private) +build FISH_VERSION ALPINE_VERSION BUILD_FROM_SOURCE="false" verbose="false": + @printf "Building Fish {{ FISH_VERSION }} from {{ if BUILD_FROM_SOURCE == "true" { "source" } else { "package" } }}\n" @printf "verbosity: %s\n\n " {{ verbose }} docker buildx build \ {{ if verbose == "verbose" {""} else { "--quiet" } }} \ --build-context alpine=docker-image://alpine:{{ ALPINE_VERSION }} \ + --build-arg FISH_VERSION={{ FISH_VERSION }} \ + --build-arg BUILD_FROM_SOURCE={{ BUILD_FROM_SOURCE }} \ --file ./Dockerfile \ - --tag=fish-{{ FISH_VERSION }} \ + --tag=fish-{{ FISH_VERSION }}.alpine-{{ ALPINE_VERSION }} \ ./ -run FISH_VERSION: +run FISH_VERSION ALPINE_VERSION: docker run \ --interactive \ --tty \ - fish-{{ FISH_VERSION }}:latest - -test FISH_VERSION: - docker run \ - --rm \ - fish-{{ FISH_VERSION }} \ - 'fish --version | grep {{ FISH_VERSION }}' - -build-3-0-2 verbose="false": (build "3.11" "3.0.2" verbose) -run-3-0-2: (run "3.0.2") - -build-3-1-2 verbose="false": (build "3.13" "3.1.2" verbose) -run-3-1-2: (run "3.1.2") - -build-3-2-2 verbose="false": (build "3.14" "3.2.2" verbose) -run-3-2-2: (run "3.2.2") - -build-3-3-1 verbose="false": (build "3.15" "3.3.1" verbose) -run-3-3-1: (run "3.3.1") - -build-3-4-1 verbose="false": (build "3.16" "3.4.1" verbose) -run-3-4-1: (run "3.4.1") - -build-3-5-1 verbose="false": (build "3.17" "3.5.1" verbose) -run-3-5-1: (run "3.5.1") - -build-3-6-1 verbose="false": (build "3.18" "3.6.1" verbose) -run-3-6-1: (run "3.6.1") - -build-3-6-3 verbose="false": (build "3.19" "3.6.3" verbose) -run-3-6-3: (run "3.6.3") - -build-3-7-1 verbose="false": (build "3.21" "3.7.1" verbose) -run-3-7-1: (run "3.7.1") - -build-4-0-2 verbose="false": (build "edge" "4.0.2" verbose) -run-4-0-2: (run "4.0.2") - -# build-3-6-2 verbose="false": (build "edge" "3.6.2" verbose) -# run-3-6-2: (run "3.6.2") \ No newline at end of file + fish-{{ FISH_VERSION }}.alpine-{{ ALPINE_VERSION }} + +test FISH_VERSION ALPINE_VERSION: + #!/usr/bin/env fish + set expected "{{ FISH_VERSION }}" + set actual (docker run --rm fish-{{ FISH_VERSION }}.alpine-{{ ALPINE_VERSION }} 'fish --version') + + if echo "$actual" | grep -q "$expected" + echo (set_color --bold --background green)"✓ fish version $expected"(set_color normal)" is correctly installed" + else + echo (set_color --bold --background red)"✗ $actual"(set_color normal)" is installed, while expecting "(set_color --background green)"$expected"(set_color normal) + exit 1 + end + +# Build and test in one command +build-and-test FISH_VERSION ALPINE_VERSION: (build FISH_VERSION ALPINE_VERSION) (test FISH_VERSION ALPINE_VERSION) \ No newline at end of file diff --git a/makefile b/makefile index a59d7ff..befe62d 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -SHELL = /usr/bin/fish +SHELL = fish CONTAINER_USER = nemo