Use composite actions (#2166)

Based on:
https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-an-action-in-the-same-repository-as-the-workflow
diff --git a/.github/actions/setup-common/action.yml b/.github/actions/setup-common/action.yml
new file mode 100644
index 0000000..344d164
--- /dev/null
+++ b/.github/actions/setup-common/action.yml
@@ -0,0 +1,51 @@
+name: 'Common setup for all OSes'
+description: 'Installs common dependencies'
+inputs:
+  codec-aom:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-dav1d:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-rav1e:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  recent-cmake:
+    description: 'Can take the values: true, false. Only useful on Linux'
+    default: 'false'
+runs:
+  using: "composite"
+  steps:
+    - name: Set up Python
+      uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+      with:
+        python-version: '3.x'
+    - name: Set up CMake < 3.18
+      if: ${{ runner.os == 'Linux' && inputs.recent-cmake == 'false' }}
+      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
+      with:
+        cmake-version: '3.13.x'
+    - name: Set up CMake >= 3.18
+      if: ${{ runner.os == 'Linux' && inputs.recent-cmake == 'true' }}
+      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
+      with:
+        cmake-version: '3.18.x'
+    - name: Print CMake version
+      run: cmake --version
+      shell: bash
+    - name: Set up ninja
+      uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
+    - name: Set up nasm
+      if: ${{ inputs.codec-aom == 'LOCAL' || inputs.codec-dav1d == 'LOCAL' }}
+      uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
+    - name: Set up meson
+      if: ${{ inputs.codec-dav1d == 'LOCAL' }}
+      run: pip install meson
+      shell: bash
+    - name: Set up rust
+      if: ${{ inputs.codec-rav1e == 'LOCAL' }}
+      uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1.0.7
+      with:
+        profile: minimal
+        toolchain: stable
+        override: true
diff --git a/.github/actions/setup-linux/action.yml b/.github/actions/setup-linux/action.yml
new file mode 100644
index 0000000..2d68fe4
--- /dev/null
+++ b/.github/actions/setup-linux/action.yml
@@ -0,0 +1,80 @@
+name: 'Setup on Linux'
+description: 'Installs dependencies and sets env variables specific to Linux'
+inputs:
+  codec-aom:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-dav1d:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    edfault: 'OFF'
+  codec-rav1e:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  extra-cache-key:
+    description: 'Extra cache key to use in the cache name. Useful when several caches are used in one workflow.'
+    default: ''
+  gcc-version:
+    description: 'Can be empty, in which case CC and CXX are not overriden'
+    default: ''
+  libxml2:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  libyuv:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  recent-cmake:
+    description: 'Can take the values: true, false'
+    default: 'false'
+outputs:
+  ext-cache-hit:
+    value: ${{ steps.cache.outputs.ext-cache-hit }}
+runs:
+  using: "composite"
+  steps:
+    - name: Install non-library dependencies
+      run: |
+        sudo apt update
+        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev
+      shell: bash
+    - name: Install libaom library
+      if: ${{ inputs.codec-aom == 'SYSTEM' }}
+      run:
+        sudo apt install libaom-dev
+      shell: bash
+    - name: Install libdav1d library
+      if: ${{ inputs.codec-dav1d == 'SYSTEM' }}
+      run:
+        sudo apt install libdav1d-dev
+      shell: bash
+    - name: Install rav1e library
+      if: ${{ inputs.codec-rav1e == 'SYSTEM' }}
+      run:
+        sudo apt install librav1e-dev
+      shell: bash
+    - name: Install libxml2 library
+      if: ${{ inputs.libxml2 == 'SYSTEM' }}
+      run:
+        sudo apt install libxml2
+      shell: bash
+    - name: Install libyuv library
+      if: ${{ inputs.libyuv == 'SYSTEM' }}
+      run:
+        sudo apt install libyuv-dev
+      shell: bash
+
+    - uses: ./.github/actions/setup-common
+      with:
+        codec-aom: ${{ inputs.codec-aom }}
+        codec-dav1d: ${{ inputs.codec-dav1d }}
+        codec-rav1e: ${{ inputs.codec-rav1e }}
+        recent-cmake: ${{ inputs.recent-cmake }}
+    - uses: ./.github/actions/cache
+      id: cache
+      with:
+        extra-key: ${{ inputs.extra-cache-key }}
+        use-rust: ${{ inputs.codec-rav1e == 'LOCAL' }}
+
+    - name: Set GCC & G++ compiler
+      if: ${{ inputs.gcc-version != '' }}
+      run: echo "CC=gcc-${{ inputs.gcc-version }}" >> $GITHUB_ENV && echo "CXX=g++-${{ inputs.gcc-version }}" >> $GITHUB_ENV
+      shell: bash
diff --git a/.github/actions/setup-macos/action.yml b/.github/actions/setup-macos/action.yml
new file mode 100644
index 0000000..19daf42
--- /dev/null
+++ b/.github/actions/setup-macos/action.yml
@@ -0,0 +1,43 @@
+name: 'Setup on macOS'
+description: 'Installs dependencies specific to macOS'
+inputs:
+  codec-aom:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-dav1d:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-rav1e:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  extra-cache-key:
+    description: 'Extra cache key to use in the cache name. Useful when several caches are used in one workflow.'
+    default: ''
+outputs:
+  ext-cache-hit:
+    value: ${{ steps.cache.outputs.ext-cache-hit }}
+runs:
+  using: "composite"
+  steps:
+    - name: Install non-library dependencies
+      run: brew install imagemagick
+      shell: bash
+    - name: Install AOM library
+      if: ${{ inputs.codec-aom == 'SYSTEM' }}
+      run: brew install aom
+      shell: bash
+    - name: Install dav1d library
+      if: ${{ inputs.codec-dav1d == 'SYSTEM' }}
+      run: brew install dav1d
+      shell: bash
+
+    - uses: ./.github/actions/setup-common
+      with:
+        codec-aom: ${{ inputs.codec-aom }}
+        codec-dav1d: ${{ inputs.codec-dav1d }}
+        codec-rav1e: ${{ inputs.codec-rav1e }}
+    - uses: ./.github/actions/cache
+      id: cache
+      with:
+        extra-key: ${{ inputs.extra-cache-key }}
+        use-rust: ${{ inputs.codec-rav1e == 'LOCAL' }}
diff --git a/.github/actions/setup-windows/action.yml b/.github/actions/setup-windows/action.yml
new file mode 100644
index 0000000..726e487
--- /dev/null
+++ b/.github/actions/setup-windows/action.yml
@@ -0,0 +1,34 @@
+name: 'Setup on Windows'
+description: 'Installs dependencies specific to Windows'
+inputs:
+  codec-aom:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-dav1d:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  codec-rav1e:
+    description: 'Can take the values: OFF, LOCAL, SYSTEM'
+    default: 'OFF'
+  extra-cache-key:
+    description: 'Extra cache key to use in the cache name. Useful when several caches are used in one workflow.'
+    default: ''
+outputs:
+  ext-cache-hit:
+    value: ${{ steps.cache.outputs.ext-cache-hit }}
+runs:
+  using: "composite"
+  steps:
+    - name: Setup Visual Studio shell
+      uses: egor-tensin/vs-shell@9a932a62d05192eae18ca370155cf877eecc2202 # v2.1
+
+    - uses: ./.github/actions/setup-common
+      with:
+        codec-aom: ${{ inputs.codec-aom }}
+        codec-dav1d: ${{ inputs.codec-dav1d }}
+        codec-rav1e: ${{ inputs.codec-rav1e }}
+    - uses: ./.github/actions/cache
+      id: cache
+      with:
+        extra-key: ${{ inputs.extra-cache-key }}
+        use-rust: ${{ inputs.codec-rav1e == 'LOCAL' }}
diff --git a/.github/workflows/ci-android-emulator-tests.yml b/.github/workflows/ci-android-emulator-tests.yml
index af07439..c2272ad 100644
--- a/.github/workflows/ci-android-emulator-tests.yml
+++ b/.github/workflows/ci-android-emulator-tests.yml
@@ -31,20 +31,11 @@
           # r25c is the same as 25.2.9519653.
           ndk-version: r25c
           add-to-path: false
-      - name: Setup ninja
-        uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-      - name: Setup cmake
-        uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
+      - uses: ./.github/actions/setup-common
         with:
-          cmake-version: "3.19.x"
-      - name: Setup python
-        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
-        with:
-          python-version: '3.x'
-      - name: Setup meson
-        run: pip install meson
-      - name: Setup nasm
-        uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
+          codec-aom: 'LOCAL'
+          codec-dav1d: 'LOCAL'
+          recent-cmake: 'true'
       - name: Setup JDK
         uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
         with:
diff --git a/.github/workflows/ci-android-jni.yml b/.github/workflows/ci-android-jni.yml
index 20113b6..94815c6 100644
--- a/.github/workflows/ci-android-jni.yml
+++ b/.github/workflows/ci-android-jni.yml
@@ -25,20 +25,11 @@
           # r25c is the same as 25.2.9519653.
           ndk-version: r25c
           add-to-path: false
-      - name: Setup ninja
-        uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-      - name: Setup cmake
-        uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
+      - uses: ./.github/actions/setup-common
         with:
-          cmake-version: "3.19.x"
-      - name: Setup python
-        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
-        with:
-          python-version: '3.x'
-      - name: Setup meson
-        run: pip install meson
-      - name: Setup nasm
-        uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
+          codec-aom: 'LOCAL'
+          codec-dav1d: 'LOCAL'
+          recent-cmake: 'true'
       - name: Setup JDK
         uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
         with:
diff --git a/.github/workflows/ci-disable-gtest.yml b/.github/workflows/ci-disable-gtest.yml
index d2843cd..f61cb0c 100644
--- a/.github/workflows/ci-disable-gtest.yml
+++ b/.github/workflows/ci-disable-gtest.yml
@@ -36,34 +36,14 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
-      if: runner.os == 'Linux'
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+    - uses: ./.github/actions/setup-linux
       with:
-        python-version: '3.x'
-    - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1.0.7
-      with:
-        profile: minimal
-        toolchain: stable
-        override: true
-    - name: Install dependencies
-      run: |
-        sudo apt update
-        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev
-    - uses: ./.github/actions/cache
-      with:
-        use-rust: true
-    - name: Setup cmake
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
-      with:
-        # CMake version 3.17 is required to build libwebp (which libsharpyuv is part of) on macOS.
-        cmake-version: '3.17.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
+        gcc-version: ${{ matrix.gcc }}
+        libyuv: 'LOCAL'
+        recent-cmake: 'true'
 
     - name: Prepare libavif (cmake)
       run: >
diff --git a/.github/workflows/ci-linux-golden-tests.yml b/.github/workflows/ci-linux-golden-tests.yml
index 9770d4c..bcc95ff 100644
--- a/.github/workflows/ci-linux-golden-tests.yml
+++ b/.github/workflows/ci-linux-golden-tests.yml
@@ -27,28 +27,17 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+    - uses: ./.github/actions/setup-linux
+      id: setup
       with:
-        python-version: '3.x'
-    - name: Install dependencies
-      run: |
-        sudo apt update
-        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev
-    - uses: ./.github/actions/cache
-      id: cache
-    - name: Setup cmake
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be
-      with:
-        # CMake 3.18 or higher is required for libxml2
-        cmake-version: '3.18.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        gcc-version: ${{ matrix.gcc }}
+        libxml2: 'LOCAL'
+        libyuv: 'LOCAL'
+        recent-cmake: 'true'
     - name: Build mp4box
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: bash -e mp4box.sh
 
diff --git a/.github/workflows/ci-linux-static-old-local.yml b/.github/workflows/ci-linux-static-old-local.yml
index 590256e..3b59430 100644
--- a/.github/workflows/ci-linux-static-old-local.yml
+++ b/.github/workflows/ci-linux-static-old-local.yml
@@ -30,35 +30,15 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+    - uses: ./.github/actions/setup-linux
       with:
-        python-version: '3.x'
-    - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1.0.7
-      with:
-        profile: minimal
-        toolchain: stable
-        override: true
-    - name: Install dependencies (Linux)
-      run: |
-        sudo apt update
-        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev
-    - uses: ./.github/actions/cache
-      with:
-        use-rust: true
-    - name: Setup cmake
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
-      with:
-        # CMake version 3.18 is required to build libxml2.
-        cmake-version: '3.18.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
-    - name: Print ImageMagick version
-      run: convert --version
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
+        gcc-version: ${{ matrix.gcc }}
+        libxml2: 'LOCAL'
+        libyuv: 'LOCAL'
+        recent-cmake: 'true'
     - name: Prepare libavif (cmake)
       run: >
         cmake .. -G Ninja -S . -B build
diff --git a/.github/workflows/ci-unix-shared-installed.yml b/.github/workflows/ci-unix-shared-installed.yml
index de43d72..a42559e 100644
--- a/.github/workflows/ci-unix-shared-installed.yml
+++ b/.github/workflows/ci-unix-shared-installed.yml
@@ -26,33 +26,24 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
+    - uses: ./.github/actions/setup-linux
       if: runner.os == 'Linux'
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
+      with:
+        codec-aom: 'SYSTEM'
+        codec-dav1d: 'SYSTEM'
+        codec-rav1e: 'OFF'
+        gcc-version: ${{ matrix.gcc }}
+        libyuv: 'SYSTEM'
+    - uses: ./.github/actions/setup-macos
+      if: runner.os == 'macOS'
+      with:
+        codec-aom: 'SYSTEM'
+        codec-dav1d: 'SYSTEM'
+        codec-rav1e: 'OFF'
     - name: Disable libyuv on macOS
       # TODO(yguyon): Install libyuv (not available with brew).
       if: runner.os == 'macOS'
       run: echo "CMAKE_AVIF_FLAGS=\"-DAVIF_LIBYUV=OFF\""  >> $GITHUB_ENV
-    - uses: ./.github/actions/cache
-    - name: Setup cmake
-      if: runner.os == 'Linux'
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
-      with:
-        cmake-version: '3.13.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - name: Install dependencies (Linux)
-      if: runner.os == 'Linux'
-      run: |
-        echo "deb http://azure.archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
-        sudo apt update
-        sudo apt install imagemagick libaom-dev libdav1d-dev libjpeg-turbo8-dev libpng-dev libyuv-dev
-    - name: Install dependencies (macOS)
-      if: runner.os == 'macOS'
-      run: brew install aom dav1d imagemagick
-    - name: Print ImageMagick version
-      run: convert --version
 
       # `sudo apt-get install googletest libgtest-dev` leads to the following:
       #   "libgtest.a(gtest-all.cc.o): undefined reference to `std::__throw_bad_array_new_length()'"
diff --git a/.github/workflows/ci-unix-shared-local.yml b/.github/workflows/ci-unix-shared-local.yml
index 092b9f3..41d9a0d 100644
--- a/.github/workflows/ci-unix-shared-local.yml
+++ b/.github/workflows/ci-unix-shared-local.yml
@@ -31,34 +31,20 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
+    - uses: ./.github/actions/setup-linux
       if: runner.os == 'Linux'
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
       with:
-        python-version: '3.x'
-    - name: Install dependencies (Linux)
-      if: runner.os == 'Linux'
-      run: |
-        sudo apt update
-        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev
-    - name: Install dependencies (macOS)
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
+        gcc-version: ${{ matrix.gcc }}
+        libyuv: ${{ matrix.libyuv }}
+    - uses: ./.github/actions/setup-macos
       if: runner.os == 'macOS'
-      run: brew install imagemagick
-    - uses: ./.github/actions/cache
-    - name: Setup cmake
-      if: runner.os == 'Linux'
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
       with:
-        # CMake version 3.17 is required to build libwebp (which libsharpyuv is part of) on macOS.
-        cmake-version: '3.17.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
-    - name: Print ImageMagick version
-      run: convert --version
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
 
     - name: Prepare libavif (cmake)
       run: >
diff --git a/.github/workflows/ci-unix-static-av2.yml b/.github/workflows/ci-unix-static-av2.yml
index cd65c91..e4d0a54 100644
--- a/.github/workflows/ci-unix-static-av2.yml
+++ b/.github/workflows/ci-unix-static-av2.yml
@@ -30,35 +30,15 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
-      if: runner.os == 'Linux'
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+    - uses: ./.github/actions/setup-linux
       with:
-        python-version: '3.x'
-    - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1.0.7
-      with:
-        profile: minimal
-        toolchain: stable
-        override: true
-    - name: Install dependencies
-      run: |
-        sudo apt update
-        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev 
-    - uses: ./.github/actions/cache
-      with:
-        use-rust: true
-        extra-key: ${{ matrix.also-enable-av1-codecs }}
-    - name: Setup cmake
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
-      with:
-        # CMake version 3.17 is required to build libwebp (which libsharpyuv is part of) on macOS.
-        cmake-version: '3.17.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
+        codec-aom: 'LOCAL'
+        codec-dav1d: ${{ matrix.also-enable-av1-codecs }}
+        codec-rav1e: ${{ matrix.also-enable-av1-codecs }}
+        extra-cache-key: ${{ matrix.also-enable-av1-codecs }}
+        gcc-version: ${{ matrix.gcc }}
+        libyuv: 'LOCAL'
+        recent-cmake: 'true'
 
     - name: Prepare libavif (cmake)
       run: >
diff --git a/.github/workflows/ci-unix-static-sanitized.yml b/.github/workflows/ci-unix-static-sanitized.yml
index 3dcd51c..0bd1aa9 100644
--- a/.github/workflows/ci-unix-static-sanitized.yml
+++ b/.github/workflows/ci-unix-static-sanitized.yml
@@ -26,39 +26,41 @@
 
     steps:
       - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-
-      - name: Setup cmake
+      - uses: ./.github/actions/setup-linux
         if: runner.os == 'Linux'
-        uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
+        id: setup_linux
         with:
-          # CMake version 3.17 is required to build libwebp (which libsharpyuv is part of) on macOS.
-          cmake-version: '3.17.x'
-      - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-      - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-      - run: pip install meson
-      - name: Install dependencies (macOS)
+          codec-aom: 'LOCAL'
+          codec-dav1d: 'LOCAL'
+          libyuv: 'LOCAL'
+          recent-cmake: 'true'
+      - uses: ./.github/actions/setup-macos
         if: runner.os == 'macOS'
-        run: brew install aom dav1d imagemagick libpng
-      - uses: ./.github/actions/cache
-        id: cache
+        id: setup_macos
         with:
-          extra-key: ${{ matrix.sanitizer }}
+          codec-aom: 'LOCAL'
+          codec-dav1d: 'LOCAL'
+          codec-rav1e: 'OFF'
+          extra-cache-key: ${{ matrix.sanitizer }}
+      - id: cache-hit
+        run: echo "hit=$${{ (runner.os == 'Linux' && steps.setup_linux.outputs.ext-cache-hit == 'true') || (runner.os == 'macOS' && steps.setup_macos.outputs.ext-cache-hit == 'true') }}" >> "$GITHUB_OUTPUT"
+
       - name: Build aom
-        if: steps.cache.outputs.ext-cache-hit != 'true'
+        if: $${{ steps.cache-hit.outputs.hit == 'false' }}
         working-directory: ./ext
         run: >
           sed -i -e 's/cmake -G Ninja \(.*\) \.\./cmake -G Ninja \1 -DSANITIZE=${{ matrix.sanitizer }} ../g' aom.cmd
 
           ./aom.cmd
       - name: Build dav1d
-        if: steps.cache.outputs.ext-cache-hit != 'true'
+        if: $${{ steps.cache-hit.outputs.hit == 'false' }}
         working-directory: ./ext
         run: >
           sed -i -e 's/meson setup \(.*\) \.\./meson setup \1 -Db_sanitize=${{ matrix.sanitizer }} -Db_lundef=false ../g' dav1d.cmd
 
           ./dav1d.cmd
       - name: Build libyuv
-        if: steps.cache.outputs.ext-cache-hit != 'true'
+        if: $${{ steps.cache-hit.outputs.hit == 'false' }}
         working-directory: ./ext
         run: ./libyuv.cmd
         env:
@@ -66,7 +68,7 @@
           CXXFLAGS: -fsanitize=${{ matrix.sanitizer }}
           LDFLAGS: -fsanitize=${{ matrix.sanitizer }}
       - name: Build libsharpyuv
-        if: steps.cache.outputs.ext-cache-hit != 'true'
+        if: $${{ steps.cache-hit.outputs.hit == 'false' }}
         working-directory: ./ext
         run: ./libsharpyuv.cmd
         env:
@@ -74,7 +76,7 @@
           CXXFLAGS: -fsanitize=${{ matrix.sanitizer }}
           LDFLAGS: -fsanitize=${{ matrix.sanitizer }}
       - name: Build GoogleTest
-        if: steps.cache.outputs.ext-cache-hit != 'true'
+        if: $${{ steps.cache-hit.outputs.hit == 'false' }}
         working-directory: ./ext
         # Note: "apt install googletest" is sometimes insufficient for find_package(GTest) so build in ext/ instead.
         run: bash -e googletest.cmd
diff --git a/.github/workflows/ci-unix-static.yml b/.github/workflows/ci-unix-static.yml
index 01ddc0a..0bcd8df 100644
--- a/.github/workflows/ci-unix-static.yml
+++ b/.github/workflows/ci-unix-static.yml
@@ -31,42 +31,23 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Set GCC & G++ compiler (on Linux)
+    - uses: ./.github/actions/setup-linux
       if: runner.os == 'Linux'
-      run: echo "CC=gcc-${{matrix.gcc}}" >> $GITHUB_ENV && echo "CXX=g++-${{matrix.gcc}}" >> $GITHUB_ENV
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
       with:
-        python-version: '3.x'
-    - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1.0.7
-      with:
-        profile: minimal
-        toolchain: stable
-        override: true
-    - name: Install dependencies (Linux)
-      if: runner.os == 'Linux'
-      run: |
-        sudo apt update
-        sudo apt install imagemagick libjpeg-turbo8-dev libpng-dev 
-    - name: Install dependencies (macOS)
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
+        gcc-version: ${{ matrix.gcc }}
+        libxml2: 'LOCAL'
+        libyuv: 'LOCAL'
+        recent-cmake: 'true'
+    - uses: ./.github/actions/setup-macos
       if: runner.os == 'macOS'
-      run: brew install imagemagick
-    - uses: ./.github/actions/cache
       with:
-        use-rust: true
-        extra-key: ${{ matrix.build-type }}
-    - name: Setup cmake
-      if: runner.os == 'Linux'
-      uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be # v2.0.2
-      with:
-        # CMake version 3.18 is required to build libxml2.
-        cmake-version: '3.18.x'
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
-    - name: Print ImageMagick version
-      run: convert --version
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
+        extra-cache-key: ${{ matrix.build-type }}
 
     - name: Prepare libavif (cmake)
       run: >
diff --git a/.github/workflows/ci-windows-artifacts.yml b/.github/workflows/ci-windows-artifacts.yml
index 07e27ac..2a4ae61 100644
--- a/.github/workflows/ci-windows-artifacts.yml
+++ b/.github/workflows/ci-windows-artifacts.yml
@@ -18,18 +18,14 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Setup Visual Studio shell
-      if: runner.os == 'Windows'
-      uses: egor-tensin/vs-shell@9a932a62d05192eae18ca370155cf877eecc2202 # v2.1
-    - uses: ./.github/actions/cache
-      id: cache
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
+    - uses: ./.github/actions/setup-windows
+      id: setup
+      with:
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+
     - name: Build aom
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./aom.cmd
       # Visual Studio 2022 has an issue starting at 17.8.0 which might cause
@@ -39,11 +35,11 @@
         CC: clang-cl
         CXX: clang-cl
     - name: Build dav1d
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./dav1d.cmd
     - name: Build libyuv
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./libyuv.cmd
       # Use clang-cl to build libyuv. The assembly code in libyuv is written in the
@@ -52,15 +48,15 @@
         CC: clang-cl
         CXX: clang-cl
     - name: Build libsharpyuv
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./libsharpyuv.cmd
     - name: Build libjpeg
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./libjpeg.cmd
     - name: Build zlib and libpng
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./zlibpng.cmd
 
diff --git a/.github/workflows/ci-windows-installed.yml b/.github/workflows/ci-windows-installed.yml
index 9fd7cfc..734c2b2 100644
--- a/.github/workflows/ci-windows-installed.yml
+++ b/.github/workflows/ci-windows-installed.yml
@@ -23,13 +23,12 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Setup Visual Studio shell
-      uses: egor-tensin/vs-shell@9a932a62d05192eae18ca370155cf877eecc2202 # v2.1
-    - name: Print CMake version
-      run: cmake --version
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - name: Print ImageMagick version
-      run: magick --version
+    - uses: ./.github/actions/setup-windows
+      with:
+        codec-aom: 'SYSTEM'
+        codec-dav1d: 'SYSTEM'
+        codec-rav1e: 'SYSTEM'
+
     - name: vcpkg build
       uses: johnwason/vcpkg-action@v6
       id: vcpkg
diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml
index a06ddbe..e67f2a0 100644
--- a/.github/workflows/ci-windows.yml
+++ b/.github/workflows/ci-windows.yml
@@ -38,30 +38,15 @@
 
     steps:
     - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
-    - name: Setup Visual Studio shell
-      if: runner.os == 'Windows'
-      uses: egor-tensin/vs-shell@9a932a62d05192eae18ca370155cf877eecc2202 # v2.1
-    - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+    - uses: ./.github/actions/setup-windows
+      id: setup
       with:
-        python-version: '3.x'
-    - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1.0.7
-      with:
-        profile: minimal
-        toolchain: stable
-        override: true
-    - uses: ./.github/actions/cache
-      id: cache
-      with:
-        use-rust: true
-    - name: Print cmake version
-      run: cmake --version
-    - uses: ilammy/setup-nasm@13cbeb366c45c4379d3478cdcbadd8295feb5028 # v1.5.1
-    - uses: seanmiddleditch/gha-setup-ninja@8b297075da4cd2a5f1fd21fe011b499edf06e9d2 # v4
-    - run: pip install meson
-    - name: Print ImageMagick version
-      run: magick --version
+        codec-aom: 'LOCAL'
+        codec-dav1d: 'LOCAL'
+        codec-rav1e: 'LOCAL'
+
     - name: Build aom
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./aom.cmd
       # Visual Studio 2022 has an issue starting at 17.8.0 which might cause
@@ -71,7 +56,7 @@
         CC: clang-cl
         CXX: clang-cl
     - name: Build libyuv
-      if: steps.cache.outputs.ext-cache-hit != 'true'
+      if: steps.setup.outputs.ext-cache-hit != 'true'
       working-directory: ./ext
       run: ./libyuv.cmd
       # Use clang-cl to build libyuv. The assembly code in libyuv is written in the