Repository: blinemedical/cerbero
Pull request #25: [VIDEO-3095] Facilitate removal of setup-gstreamer by splitting gstreamer action into pieces
Author: login: btgoodwin
Created: 2025-03-11T17:10:55Z | Merged: 2025-03-28T12:35:35Z

VIDEO-3095:

The goal of the ticket and its subtasks is to make it more straight-forward to directly use our cerbero build action(s) with other projects vs. how we were using `setup-gstreamer` to download and install MSIs.

1. `setup-gstreamer`: Configures variables, identifies version numbers, etc. and looks for each installer on S3.  If installers are found, the build is skipped and the installers are downloaded and ran.  Otherwise, the build is run and the locally-held installers are installed.
2. `build-gstreamer`: Formerly build-gstreamer-for-windows is now stripped to the studs of bootstrapping the environment, running the build, and uploading to s3 (optionally).
3. `install-gstreamer`: Installs the MSIs from either the local '-path' or downloads and then installs from the '-url' variables.

External projects should point to `setup-gstreamer`.

Testing:

1. [Job where not-found-build-install worked](https://github.com/blinemedical/cerbero/actions/runs/13929835453/attempts/1)
2. [Job run where find-download-install worked](https://github.com/blinemedical/cerbero/actions/runs/13929835453/job/38988886526)
3. [Job where force-build-install worked](https://github.com/blinemedical/cerbero/actions/runs/13930896358/job/38989469923)

> Note to future self (and others): in order to "test" 1->2, go to the successful (1) and re-run all jobs.  This will ensure that the commit sha used for the upload in (1) and the sha picked up by (2) are identical because (1) was initiated using a merge request SHA, not the branch head.  If you go to Actions and manually run the action, that's basically (3) because it's going to grab the branch head.


Diff:
diff --git a/.github/actions/build-gstreamer-for-windows/action.yml b/.github/actions/build-gstreamer/action.yml
similarity index 50%
rename from .github/actions/build-gstreamer-for-windows/action.yml
rename to .github/actions/build-gstreamer/action.yml
index b1d0ff80e..8d8e612a5 100644
--- a/.github/actions/build-gstreamer-for-windows/action.yml
+++ b/.github/actions/build-gstreamer/action.yml
@@ -1,14 +1,10 @@
 name: Build GStreamer with Cerbero on Windows
 description: Uses the GStreamer Cerbero build aggregator to build GStreamer MSI packages on the local toolchain
 inputs:
-  cerbero-repo:
-    description: 'Repository to use for Cerbero'
+  cerbero-package-origin:
+    description: 'Package Origin to set in GStreamer Packages'
     required: false
-    default: ${{ github.action_repository || github.repository }}
-  cerbero-ref:
-    description: 'Ref to use for Cerbero'
-    required: false
-    default: ${{ github.action_ref || github.ref }}
+    default: ''
   config:
     description: 'Name of the configuration file to use'
     required: false
@@ -25,9 +21,6 @@ inputs:
     description: 'Whether to bootstrap the system'
     required: false
     default: 'false'
-  s3-download-paths:
-    description: 'S3 paths to download from'
-    required: false
   s3-upload-path:
     description: 'S3 path to upload to'
     required: false
@@ -37,142 +30,24 @@ inputs:
   gst-plugins-rs-ref:
     description: 'Ref/tag to use for gst-plugins-rs'
     required: false
-  force:
-    description: 'Whether to force the build'
-    required: false
-  cleanup:
-    description: 'Whether to clean the Cerbero build directory'
-    required: false
-    default: 'true'
   no-cache:
     description: 'Whether to disable restoring from cache'
     required: false
     default: 'false'
 outputs:
   gstreamer-version:
-    description: 'GStreamer version'
-    value: ${{ steps.version-info.outputs.gstreamer-version }}
-  cerbero-short-sha:
-    description: 'Short SHA of the Cerbero checkout'
-    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
-  cerbero-path:
-    description: 'Path to the Cerbero checkout'
-    value: ${{ inputs.cleanup != 'true' && 'cerbero' }}
+    description: 'GStreamer version built'
+    value: ${{ steps.cerbero-config.outputs.version }}
   runtime-installer-path:
     description: 'Location of the GStreamer runtime installer'
     value: ${{ steps.build-packages.outputs.runtime-msi }}
-  runtime-installer-url:
-    description: 'Presigned S3 URL of the GStreamer runtime installer if s3-upload-path is set'
-    value: ${{ steps.presign-urls.outputs.runtime-url }}
   devel-installer-path:
     description: 'Location of the GStreamer development installer'
     value: ${{ steps.build-packages.outputs.dev-msi }}
-  devel-installer-url:
-    description: 'Presigned S3 URL of the GStreamer development installer if s3-upload-path is set'
-    value: ${{ steps.presign-urls.outputs.dev-url }}
 runs:
   using: "composite"
   steps:
-
-    - id: setup-msys2
-      uses: msys2/setup-msys2@v2
-      with:
-        msystem: UCRT64
-        path-type: inherit
-        release: false
-        install: >-
-          mingw-w64-ucrt-x86_64-jq
-          m4
-          mingw-w64-ucrt-x86_64-gcc-libs
-          mingw-w64-ucrt-x86_64-libwinpthread-git
-          bison
-          mingw-w64-ucrt-x86_64-diffutils
-          flex
-          mingw-w64-ucrt-x86_64-gperf
-          mingw-w64-ucrt-x86_64-make
-          mingw-w64-ucrt-x86_64-ninja
-          mingw-w64-ucrt-x86_64-perl
-
-    - shell: msys2 -eo pipefail {0}
-      run: git config --global core.autocrlf false
-
-    - id: get-cerbero
-      name: Get Cerbero
-      uses: actions/checkout@v4
-      with:
-        repository: ${{ inputs.cerbero-repo }}
-        path: cerbero
-        ref: ${{ inputs.cerbero-ref }}
-
-    - id: version-info
-      shell: msys2 -eo pipefail {0}
-      working-directory: cerbero
-      env:
-        CI_PROJECT_NAME: cerbero
-      run: |
-        # Get GStreamer/cerbero version info and installer filenames
-        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
-        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
-
-        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
-        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
-
-        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
-        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
-
-    - id: check-installer-exists
-      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
-      shell: msys2 -eo pipefail {0}
-      continue-on-error: true
-      env:
-        RUNTIME_MSI_FILENAME: ${{ steps.version-info.outputs.runtime-msi-filename }}
-        DEV_MSI_FILENAME: ${{ steps.version-info.outputs.dev-msi-filename }}
-        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
-      run: |
-        # Check installer already exists in S3
-
-        runtime_msi="null"
-        dev_msi="null"
-
-        for download_path in ${S3_DOWNLOAD_PATHS}; do
-          echo "Checking for installers under ${download_path}"
-
-          bucket=$(echo ${download_path} | cut -d/ -f3)
-          prefix=$(echo ${download_path} | cut -d/ -f4-)
-
-          if [ "$runtime_msi" = "null" ]; then
-            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
-            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
-            if [ "$runtime_msi" != "null" ]; then
-              echo "runtime-msi=s3://${bucket}/${runtime_msi}" | tee -a $GITHUB_OUTPUT
-            fi
-          fi
-
-          if [ "$dev_msi" = "null" ]; then
-            echo "Checking for development installer under s3://${bucket}/${prefix}/"
-            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
-            if [ "$dev_msi" != "null" ]; then
-              echo "dev-msi=s3://${bucket}/${dev_msi}" | tee -a $GITHUB_OUTPUT
-            fi
-          fi
-
-          if [ "$runtime_msi" != "null" ] && [ "$dev_msi" != "null" ]; then
-            break
-          fi
-        done
-
-    - name: Set variables
-      id: variables
-      shell: msys2 -eo pipefail {0}
-      run: |
-        # Set variables for later steps
-        if [ -z "${{ steps.check-installer-exists.outputs.runtime-msi }}" ] ||
-          [ -z "${{ steps.check-installer-exists.outputs.dev-msi }}" ];
-        then
-          echo "do-build=true" >> $GITHUB_OUTPUT
-        fi
-
-    - if: ${{ steps.variables.outputs.do-build && inputs.bootstrap-system == 'true' }}
+    - if: ${{ inputs.bootstrap-system == 'true' }}
       shell: msys2 -eo pipefail {0}
       working-directory: cerbero
       run: |
@@ -180,7 +55,6 @@ runs:
         powershell.exe ./tools/bootstrap-windows.ps1
 
     - id: cerbero-config
-      if: ${{ steps.variables.outputs.do-build }}
       working-directory: cerbero
       shell: msys2 -eo pipefail {0}
       env:
@@ -190,7 +64,7 @@ runs:
         CERBERO: "./cerbero-uninstalled -c config/${{ inputs.config }} -c localconf.cbc"
         CERBERO_ARGS: ${{ inputs.cerbero-args }}
         CERBERO_PACKAGE_ARGS: ${{ inputs.cerbero-package-args }}
-        CERBERO_PACKAGE_ORIGIN: ${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.version-info.outputs.cerbero-short-sha }}
+        CERBERO_PACKAGE_ORIGIN: ${{ inputs.cerbero-package-origin }}
         GST_PLUGINS_RS_SOURCE: ${{ inputs.gst-plugins-rs-repo }}
         GST_PLUGINS_RS_REF: ${{ inputs.gst-plugins-rs-ref }}
       run: |
@@ -224,28 +98,31 @@ runs:
         config_hash=$(echo "${CERBERO_ARGS}${{ inputs.config }}${GST_PLUGINS_RS_SOURCE}${GST_PLUGINS_RS_REF}" | sha256sum | cut -d' ' -f1)
         echo "config-hash=${config_hash}" >> $GITHUB_OUTPUT
 
+        # Get GStreamer version from cerbero
+        echo "version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')" >> $GITHUB_OUTPUT
+
     - id: restore-cerbero-sources-cache
-      if: ${{ steps.variables.outputs.do-build && inputs.no-cache != 'true' }}
+      if: ${{ inputs.no-cache != 'true' }}
       uses: actions/cache/restore@v4
       with:
         path: ${{ steps.cerbero-config.outputs.cerbero-sources }}
         key: |
-          cerbero-sources-${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.cerbero-config.outputs.cerbero-sha }}
+          cerbero-sources-${{ steps.cerbero-config.outputs.version }}-${{ steps.cerbero-config.outputs.cerbero-sha }}
         restore-keys: |
-          cerbero-sources-${{ steps.version-info.outputs.gstreamer-version }}-
+          cerbero-sources-${{ steps.cerbero-config.outputs.version }}-
           cerbero-sources-
 
     - id: restore-cerbero-deps-cache
-      if: ${{ steps.variables.outputs.do-build && inputs.no-cache != 'true' }}
+      if: ${{ inputs.no-cache != 'true' }}
       uses: actions/cache/restore@v4
       with:
         path: ${{ steps.cerbero-config.outputs.cerbero-home }}\cerbero-deps.tar.xz
         key: |
-          ${{ runner.os }}-cerbero-deps-${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.cerbero-config.outputs.cerbero-sha }}-${{ steps.cerbero-config.outputs.config-hash }}-${{ runner.name }}
+          ${{ runner.os }}-cerbero-deps-${{ steps.cerbero-config.outputs.version }}-${{ steps.cerbero-config.outputs.cerbero-sha }}-${{ steps.cerbero-config.outputs.config-hash }}-${{ runner.name }}
         restore-keys: |
-          ${{ runner.os }}-cerbero-deps-${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.cerbero-config.outputs.cerbero-sha }}-${{ steps.cerbero-config.outputs.config-hash }}-
+          ${{ runner.os }}-cerbero-deps-${{ steps.cerbero-config.outputs.version }}-${{ steps.cerbero-config.outputs.cerbero-sha }}-${{ steps.cerbero-config.outputs.config-hash }}-
 
-    - if: ${{ steps.variables.outputs.do-build && steps.restore-cerbero-deps-cache.outputs.cache-matched-key }}
+    - if: ${{ steps.restore-cerbero-deps-cache.outputs.cache-matched-key }}
       shell: msys2 -eo pipefail {0}
       run: |
         # Restore Cerbero dependencies
@@ -262,7 +139,6 @@ runs:
         echo "::endgroup::"
 
     - id: bootstrap-cerbero
-      if: ${{ steps.variables.outputs.do-build }}
       working-directory: cerbero
       shell: msys2 -eo pipefail {0}
       env:
@@ -310,7 +186,6 @@ runs:
         key: ${{ steps.restore-cerbero-deps-cache.outputs.cache-primary-key }}
 
     - id: build-packages
-      if: ${{ steps.variables.outputs.do-build }}
       working-directory: cerbero
       shell: msys2 -eo pipefail {0}
       env:
@@ -326,32 +201,10 @@ runs:
         find ~+ -maxdepth 1 -name 'gstreamer-1.0-msvc*.msi' -exec sh -c 'echo runtime-msi=$(cygpath -aw "{}") >> $GITHUB_OUTPUT' \;
         find ~+ -maxdepth 1 -name 'gstreamer-1.0-devel*.msi' -exec sh -c 'echo dev-msi=$(cygpath -aw "{}") >> $GITHUB_OUTPUT' \;
 
-    - id: upload-packages
-      if: ${{ steps.build-packages.outcome == 'success' && inputs.s3-upload-path }}
+    - name: Annotate workflow run with GStreamer version
       shell: msys2 -eo pipefail {0}
-      working-directory: cerbero
-      run: |
-        echo "::group::Upload packages"
-
-        upload_path="${{ inputs.s3-upload-path }}/${{ steps.version-info.outputs.runtime-msi-filename }}"
-        aws s3 cp --no-progress "$(cygpath -u '${{ steps.build-packages.outputs.runtime-msi }}')" "${upload_path}"
-        echo "::notice title=GStreamer Runtime Installer Uploaded to S3::Location: ${upload_path}"
-        echo "runtime-msi=${upload_path}" | tee -a $GITHUB_OUTPUT
-
-        upload_path="${{ inputs.s3-upload-path }}/${{ steps.version-info.outputs.dev-msi-filename }}"
-        aws s3 cp --no-progress "$(cygpath -u '${{ steps.build-packages.outputs.dev-msi }}')" "${upload_path}"
-        echo "::notice title=GStreamer Development Installer Uploaded to S3::Location: ${upload_path}"
-        echo "dev-msi=${upload_path}" | tee -a $GITHUB_OUTPUT
-
-        echo "::endgroup::"
-
-    - id: presign-urls
-      if: ${{ (steps.check-installer-exists.outputs.runtime-msi && steps.check-installer-exists.outputs.dev-msi) || steps.upload-packages.outcome == 'success' }}
-      shell: bash
       run: |
-        # Presign URLs for installers
-        echo "runtime-url=$(aws s3 presign '${{ steps.check-installer-exists.outputs.runtime-msi || steps.upload-packages.outputs.runtime-msi }}')" | tee -a $GITHUB_OUTPUT
-        echo "dev-url=$(aws s3 presign '${{ steps.check-installer-exists.outputs.dev-msi || steps.upload-packages.outputs.dev-msi }}')" | tee -a $GITHUB_OUTPUT
+        echo "::notice title=Built GStreamer version ${{ steps.cerbero-config.outputs.version }}::Created installer with Cerbero short sha ${{ steps.cerbero-config.outputs.cerbero-sha }}"
 
     - uses: actions/upload-artifact@v4
       if: ${{ steps.build-packages.outputs.runtime-msi || steps.build-packages.outputs.dev-msi }}
@@ -367,8 +220,3 @@ runs:
       with:
         name: cerbero-logs
         path: ${{ steps.cerbero-config.outputs.cerbero-home }}\logs
-
-    - name: Cleanup Cerbero
-      if: ${{ always() && inputs.cleanup == 'true' && steps.get-cerbero.outcome != 'skipped' }}
-      shell: msys2 -eo pipefail {0}
-      run: rm -rf "cerbero"
diff --git a/.github/actions/install-gstreamer/action.yml b/.github/actions/install-gstreamer/action.yml
new file mode 100644
index 000000000..0827059f7
--- /dev/null
+++ b/.github/actions/install-gstreamer/action.yml
@@ -0,0 +1,129 @@
+name: Install GStreamer with Cerbero on Windows
+description: Installs the GStreamer MSIs either from the URLs or local paths
+inputs:
+  runtime-installer-url:
+    description: 'URL of the GStreamer runtime installer'
+    required: false
+  development-installer-url:
+    description: 'URL of the GStreamer development installer'
+    required: false
+  runtime-installer-path:
+    description: 'Local path of the GStreamer runtime installer'
+    required: false
+  development-installer-path:
+    description: 'Local path of the GStreamer development installer'
+    required: false
+outputs:
+  install-dir:
+    description: 'GStreamer install directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-install-dir }}
+  plugin-dir:
+    description: 'GStreamer plugin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-plugin-dir }}
+  bin-dir:
+    description: 'GStreamer bin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-bin-dir }}
+
+runs:
+  using: "composite"
+  steps:
+    # Mutual exclusive -- either set url or path for each.  Error if both or neither
+    # are set.  If path is set, verify the file exists.
+    - if: ${{ inputs.runtime-installer-url && inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both runtime-installer-url and runtime-installer-path"
+        exit 1
+    - if: ${{ inputs.development-installer-url && inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both development-installer-url and development-installer-path"
+        exit 2
+    - if: ${{ !inputs.runtime-installer-url && !inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Must specify either runtime-installer-url or runtime-installer-path"
+        exit 3
+    - if: ${{ !inputs.development-installer-url && !inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Must specify either development-installer-url or development-installer-path"
+        exit 4
+    - if: ${{ inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        if [[ ! -f "${{ inputs.runtime-installer-path }}" ]]; then
+          echo "Error: Runtime installer path does not exist: ${{ inputs.runtime-installer-path }}"
+          exit 5
+        fi
+    - if: ${{ inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        if [[ ! -f "${{ inputs.development-installer-path }}" ]]; then
+          echo "Error: Development installer path does not exist: ${{ inputs.development-installer-path }}"
+          exit 6
+        fi
+
+    # If the URL is provided, download the MSI, set path.
+    # This will error out if the file is missing.
+    - if: ${{ !inputs.runtime-installer-path }}
+      id: download-runtime
+      name: Download GStreamer Runtime MSI
+      shell: powershell
+      run: |
+        $msi_path = "$(( $pwd ))\gstreamer-runtime.msi"
+        Invoke-WebRequest -Uri "${{ inputs.runtime-installer-url }}" -OutFile "$msi_path"
+        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+        echo "::notice title=GStreamer runtime installer downloaded::Downloaded GStreamer runtime installer from ${{ inputs.runtime-installer-url }}"
+    - if: ${{ !inputs.development-installer-path }}
+      id: download-development
+      name: Download GStreamer Development MSI
+      shell: powershell
+      run: |
+        $msi_path = "$(( $pwd ))\gstreamer-development.msi"
+        Invoke-WebRequest -Uri "${{ inputs.development-installer-url }}" -OutFile "$msi_path"
+        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+        echo "::notice title=GStreamer development installer downloaded::Downloaded GStreamer development installer from ${{ inputs.development-installer-url }}"  
+
+    # Set the local paths favoring if the download happened
+    - id: msi-path
+      shell: powershell
+      run: |
+        # If the download happened, use the downloaded path, else use the input path
+        $runtime = "${{ steps.download-development.outputs.msi-path }}"
+        if ($runtime -eq "") {
+          $runtime = "${{ inputs.runtime-installer-path }}"
+        }
+        $development = "${{ steps.download-development.outputs.msi-path }}"
+        if ($development -eq "") {
+          $development = "${{ inputs.development-installer-path }}"
+        }
+        echo "runtime=$runtime" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+        echo "development=$development" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+
+    - name: Install GStreamer
+      id: install-gstreamer
+      shell: powershell
+      env:
+        RUNTIME_MSI: ${{ steps.msi-path.outputs.runtime }}
+        DEVELOPMENT_MSI: ${{ steps.msi-path.outputs.development }}
+      run: |
+        $GSTREAMER_INSTALL_DIR="${{ runner.temp }}\gstreamer"
+        $GSTREAMER_ROOT="$GSTREAMER_INSTALL_DIR\1.0\msvc_x86_64"
+        $GSTREAMER_BIN="$GSTREAMER_ROOT\bin"
+        echo "$GSTREAMER_BIN" | Out-File -FilePath $Env:GITHUB_PATH -Append -Encoding utf8
+        echo "PKG_CONFIG_PATH=$GSTREAMER_ROOT\lib\pkgconfig;$env:PKG_CONFIG_PATH" | Out-File -FilePath $Env:GITHUB_ENV -Append -Encoding utf8
+
+        echo "gstreamer-bin-dir=$GSTREAMER_BIN" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+        echo "gstreamer-install-dir=$GSTREAMER_INSTALL_DIR" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+        echo "gstreamer-plugin-dir=$GSTREAMER_ROOT" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+
+        if ($RUNTIME_MSI) {
+          echo "Installing GStreamer Runtime"
+          Start-Process msiexec -ArgumentList '/i `"$RUNTIME_MSI`" ADDLOCAL=ALL INSTALLDIR=`"$GSTREAMER_INSTALL_DIR`" /qn' -NoNewWindow -PassThru -Wait
+
+          if ($DEVELOPMENT_MSI) {
+            echo "Installing GStreamer Development"
+            Start-Process msiexec -ArgumentList '/i `"$DEVELOPMENT_MSI`" ADDLOCAL=ALL INSTALLDIR=`"$GSTREAMER_INSTALL_DIR`" /qn' -NoNewWindow -PassThru -Wait
+          }
+        }
diff --git a/.github/actions/setup-gstreamer/action.yml b/.github/actions/setup-gstreamer/action.yml
new file mode 100644
index 000000000..40102aa0d
--- /dev/null
+++ b/.github/actions/setup-gstreamer/action.yml
@@ -0,0 +1,213 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+        RUNTIME_MSI_FILENAME: ${{ steps.version-info.outputs.runtime-msi-filename }}
+        DEV_MSI_FILENAME: ${{ steps.version-info.outputs.dev-msi-filename }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              s3_uri="s3://${bucket}/${runtime_msi}"
+              aws s3 cp --no-progress "${s3_uri}" "cerbero/$(basename ${runtime_msi})"
+              echo "runtime-path=cerbero/$(basename ${runtime_msi})" | tee -a $GITHUB_OUTPUT
+              echo "::notice title=GStreamer Runtime Installer Downloaded From S3::Location: ${s3_uri}"
+            fi
+          fi
+
+          if [ "$dev_msi" = "null" ]; then
+            echo "Checking for development installer under s3://${bucket}/${prefix}/"
+            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$dev_msi" != "null" ]; then
+              s3_uri="s3://${bucket}/${dev_msi}"
+              aws s3 cp --no-progress "${s3_uri}" "cerbero/$(basename ${dev_msi})"
+              echo "dev-path=cerbero/$(basename ${dev_msi})" | tee -a $GITHUB_OUTPUT
+              echo "::notice title=GStreamer Development Installer Downloaded From S3::Location: ${s3_uri}"
+            fi
+          fi
+
+          if [ "$runtime_msi" != "null" ] && [ "$dev_msi" != "null" ]; then
+            break
+          fi
+        done
+
+      # If either installer was not found, run a build.
+    - id: build-packages
+      if: ${{ !steps.check-installer-exists.outputs.runtime-path || !steps.check-installer-exists.outputs.dev-path }}
+      name: Build GStreamer with Cerbero on Windows
+      uses: ./.github/actions/build-gstreamer
+      with:
+        config: ${{ inputs.config }}
+        cerbero-args: ${{ inputs.cerbero-args }}
+        cerbero-package-args: ${{ inputs.cerbero-package-args }}
+        cerbero-package-origin: ${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.version-info.outputs.cerbero-short-sha }}
+        bootstrap-system: ${{ inputs.bootstrap-system }}
+        gst-plugins-rs-repo: ${{ inputs.gst-plugins-rs-repo }}
+        gst-plugins-rs-ref: ${{ inputs.gst-plugins-rs-ref }}
+        no-cache: ${{ inputs.no-cache }}
+
+      # If the build ran, upload the result (if possible).
+    - id: upload-packages
+      if: ${{ steps.build-packages.outcome == 'success' && inputs.s3-upload-path }}
+      name: Upload newly-built GStreamer installers to S3
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      env:
+        RUNTIME_MSI_FILENAME: ${{ steps.version-info.outputs.runtime-msi-filename }}
+        DEV_MSI_FILENAME: ${{ steps.version-info.outputs.dev-msi-filename }}
+      run: |
+        echo "::group::Upload packages"
+
+        upload_path="${{ inputs.s3-upload-path }}/${RUNTIME_MSI_FILENAME}"
+        aws s3 cp --no-progress "$(cygpath -u '${{ steps.build-packages.outputs.runtime-installer-path }}')" "${upload_path}"
+        echo "::notice title=GStreamer Runtime Installer Uploaded to S3::Location: ${upload_path}"
+        echo "runtime-msi=${upload_path}" | tee -a $GITHUB_OUTPUT
+
+        upload_path="${{ inputs.s3-upload-path }}/${DEV_MSI_FILENAME}"
+        aws s3 cp --no-progress "$(cygpath -u '${{ steps.build-packages.outputs.devel-installer-path }}')" "${upload_path}"
+        echo "::notice title=GStreamer Development Installer Uploaded to S3::Location: ${upload_path}"
+        echo "dev-msi=${upload_path}" | tee -a $GITHUB_OUTPUT
+
+        echo "::endgroup::"
+
+    - id: select-path
+      shell: msys2 -eo pipefail {0}
+      env:
+        RUNTIME_PATH: ${{ steps.build-packages.outputs.runtime-installer-path || steps.check-installer-exists.outputs.runtime-path }}
+        DEV_PATH: ${{ steps.build-packages.outputs.devel-installer-path || steps.check-installer-exists.outputs.dev-path }}
+      run: |
+        echo "runtime-installer-path=${RUNTIME_PATH}" | tee -a $GITHUB_OUTPUT
+        echo "development-installer-path=${DEV_PATH}" | tee -a $GITHUB_OUTPUT
+
+      # Install the gstreamer MSIs
+    - id: install
+      name: Install GStreamer on Windows
+      uses: ./.github/actions/install-gstreamer
+      with:
+        runtime-installer-path: ${{ steps.select-path.outputs.runtime-installer-path }}
+        development-installer-path: ${{ steps.select-path.outputs.development-installer-path }}
+
+    - name: Cleanup Cerbero
+      if: ${{ always() && inputs.cleanup == 'true' && steps.get-cerbero.outcome != 'skipped'}}
+      shell: msys2 -eo pipefail {0}
+      run: rm -rf cerbero
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 319ef5ca4..396db65ea 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -59,12 +59,12 @@ jobs:
         uses: actions/checkout@v4
         with:
           sparse-checkout: |
-            .github/actions/build-gstreamer-for-windows
+            .github/actions/**
           sparse-checkout-cone-mode: false
 
-      - id: build-gstreamer
-        name: Build GStreamer
-        uses: ./.github/actions/build-gstreamer-for-windows
+      - id: setup-gstreamer
+        name: Setup GStreamer
+        uses: ./.github/actions/setup-gstreamer
         with:
           cerbero-repo: ${{ inputs.cerbero-repo || github.repository }}
           cerbero-ref: ${{ inputs.cerbero-ref || github.ref }}
@@ -79,8 +79,3 @@ jobs:
             ${{ vars.GSTREAMER_S3_UPLOAD_BUCKET && steps.s3-prefix.outcome != 'skipped' && format('s3://{0}/{1}', vars.GSTREAMER_S3_UPLOAD_BUCKET, steps.s3-prefix.outputs.prefix) || '' }}
           s3-upload-path: >-
             ${{ vars.GSTREAMER_S3_UPLOAD_BUCKET && vars.GSTREAMER_S3_UPLOAD_PREFIX && format('s3://{0}/{1}', vars.GSTREAMER_S3_UPLOAD_BUCKET, vars.GSTREAMER_S3_UPLOAD_PREFIX) || '' }}
-
-      - name: Annotate workflow run with GStreamer version
-        if: steps.build-gstreamer.outputs.gstreamer-version
-        run: |
-          echo "::notice title=Built GStreamer version ${{ steps.build-gstreamer.outputs.gstreamer-version }}::Created installer with Cerbero short sha ${{ steps.build-gstreamer.outputs.cerbero-short-sha }}"


Reviews:
user: login: jawilson
body: I do prefer option (2). I don't think we have to validate the Debian side right away, but worth throwing a ticket in. I'm not sure the S3 stuff will be very widely used, but it's very much optional (just don't specify any `s3-*` inputs).
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-18T00:01:17Z
user: login: btgoodwin
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T12:52:57Z
user: login: btgoodwin
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T13:03:18Z
user: login: btgoodwin
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T13:07:14Z
user: login: jawilson
body: Ran and re-ran and behavior was as expected! Just a few comments to address and this will be good to go.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-24T18:17:48Z
user: login: jawilson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T16:34:08Z
user: login: jawilson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T16:35:25Z
user: login: jawilson
body: I missed one last set of annotations and then we should be good!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-27T16:37:02Z
user: login: jawilson
body: Bah, VS Code PR interface messed with me again, meant to request changes on that last one.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-27T16:37:46Z
user: login: jawilson
body: Excellent work!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-27T21:20:13Z

Review comments:
diff_hunk: @@ -0,0 +1,101 @@
+name: Install GStreamer with Cerbero on Windows
+description: Installs the GStreamer MSIs either from the URLs or local paths
+inputs:
+  runtime-installer-url:
+    description: 'URL of the GStreamer runtime installer'
+    required: false
+  development-installer-url:
+    description: 'URL of the GStreamer development installer'
+    required: false
+  runtime-installer-path:
+    description: 'Local path of the GStreamer runtime installer'
+    required: false
+  development-installer-path:
+    description: 'Local path of the GStreamer development installer'
+    required: false
+outputs:
+  install-dir:
+    description: 'GStreamer install directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-install-dir }}
+  plugin-dir:
+    description: 'GStreamer plugin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-plugin-dir }}
+  bin-dir:
+    description: 'GStreamer bin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-bin-dir }}
+
+runs:
+  using: "composite"
+  steps:
+    # Mutual exclusive -- either set url or path for each.  Error if both.
+    - if: ${{ inputs.runtime-installer-url && inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both runtime-installer-url and runtime-installer-path"
+        exit 1
+    - if: ${{ inputs.development-installer-url && inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both development-installer-url and development-installer-path"
+        exit 2
path: .github/actions/install-gstreamer/action.yml
user: login: jawilson
body: We should also error if neither is specified, or if the file specified by `inputs.runtime-installer-path` doesn't exist.
created_at: 2025-03-17T18:57:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,177 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              echo "runtime-url=$(aws s3 presign s3://${bucket}/${runtime_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$dev_msi" = "null" ]; then
+            echo "Checking for development installer under s3://${bucket}/${prefix}/"
+            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
path: .github/actions/setup-gstreamer/action.yml
user: login: jawilson
body: `RUNTIME_MSI_FILENAME` and `DEV_MSI_FILENAME` are both undefined here, so I think it's just finding the first build to end with an empty string, which would be all of them. Each time you push to your branch a full build should be done because it's a whole new [`cerbero_short_sha`](https://github.com/blinemedical/cerbero/blob/18ec28f35700510ce693d36dc13e261f75758fc1/.github/actions/setup-gstreamer/action.yml#L101). I think you just need to pass them in like the old action did:
```yaml
      env:
        RUNTIME_MSI_FILENAME: ${{ steps.version-info.outputs.runtime-msi-filename }}
        DEV_MSI_FILENAME: ${{ steps.version-info.outputs.dev-msi-filename }}
```
created_at: 2025-03-17T23:03:41Z
author_association: MEMBER
diff_hunk: @@ -81,6 +81,6 @@ jobs:
             ${{ vars.GSTREAMER_S3_UPLOAD_BUCKET && vars.GSTREAMER_S3_UPLOAD_PREFIX && format('s3://{0}/{1}', vars.GSTREAMER_S3_UPLOAD_BUCKET, vars.GSTREAMER_S3_UPLOAD_PREFIX) || '' }}
 
       - name: Annotate workflow run with GStreamer version
-        if: steps.build-gstreamer.outputs.gstreamer-version
+        if: steps.setupo-gstreamer.outputs.gstreamer-version
path: .github/workflows/windows.yml
user: login: jawilson
body: Typo, which definitely made me chuckle a bit
```suggestion
        if: steps.setup-gstreamer.outputs.gstreamer-version
```
created_at: 2025-03-17T23:09:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,177 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              echo "runtime-url=$(aws s3 presign s3://${bucket}/${runtime_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$dev_msi" = "null" ]; then
+            echo "Checking for development installer under s3://${bucket}/${prefix}/"
+            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$dev_msi" != "null" ]; then
+              echo "dev-url=$(aws s3 presign s3://${bucket}/${dev_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$runtime_msi" != "null" ] && [ "$dev_msi" != "null" ]; then
+            break
+          fi
+        done
+
+      # If either installer was not found, run a build.
+    - id: build
+      if: ${{ !steps.check-installer-exists.outputs.runtime-url || !steps.check-installer-exists.outputs.dev-url }}
+      name: Build GStreamer with Cerbero on Windows
+      uses: ./.github/actions/build-gstreamer
+      with:
+        config: ${{ inputs.config }}
+        cerbero-args: ${{ inputs.cerbero-args }}
+        cerbero-package-args: ${{ inputs.cerbero-package-args }}
+        cerbero-package-origin: ${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.version-info.outputs.cerbero-short-sha }}
+        bootstrap-system: ${{ inputs.bootstrap-system }}
+        gst-plugins-rs-repo: ${{ inputs.gst-plugins-rs-repo }}
+        gst-plugins-rs-ref: ${{ inputs.gst-plugins-rs-ref }}
+        runtime-msi-filename: ${{ steps.version-info.outputs.runtime-msi-filename }}
+        dev-msi-filename: ${{ steps.version-info.outputs.dev-msi-filename }}
+        force: ${{ inputs.force }}
+        cleanup: ${{ inputs.cleanup }}
+        no-cache: ${{ inputs.no-cache }}
path: .github/actions/setup-gstreamer/action.yml
user: login: jawilson
body: The necessary args to upload a build to S3 (`s3-download-paths` and `s3-upload-path`) aren't specified so the build is never uploaded.
That said, it feels to me like uploading builds to S3 should be moved into this action since it's also the one attempting to download them. That would also make the `build-gstreamer` action _only_ build GStreamer. Additionally, because we're now directly using the installers created by `build-gstreamer` there's no need to generate any pre-signed URLs after uploading the installers.
created_at: 2025-03-17T23:15:59Z
author_association: MEMBER
path: .github/actions/build-gstreamer/action.yml
user: login: jawilson
body: Comments in this file from lines that haven't been changed (and thus GH won't let me comment on):

- The following inputs aren't used in this action:
  - `cerbero-repo`
  - `cerbero-ref`
  - `s3-download-paths`
  - `force`
  - `inputs.cleanup` - technically used in `outputs.cerbero-path` but that's deceptive
- And if you move uploading to S3 to the `setup-gstreamer` action these will also be unused (you can also get rid of the `*-installer-url` outputs):
  - `s3-upload-path`
  - `runtime-msi-filename`
  - `dev-msi-filename`
- `gstreamer-version` should be inferred within this action with `./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}'` even if it duplicates from `setup-gstreamer`
created_at: 2025-03-17T23:22:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,177 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              echo "runtime-url=$(aws s3 presign s3://${bucket}/${runtime_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$dev_msi" = "null" ]; then
+            echo "Checking for development installer under s3://${bucket}/${prefix}/"
+            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$dev_msi" != "null" ]; then
+              echo "dev-url=$(aws s3 presign s3://${bucket}/${dev_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$runtime_msi" != "null" ] && [ "$dev_msi" != "null" ]; then
+            break
+          fi
+        done
+
+      # If either installer was not found, run a build.
+    - id: build
+      if: ${{ !steps.check-installer-exists.outputs.runtime-url || !steps.check-installer-exists.outputs.dev-url }}
+      name: Build GStreamer with Cerbero on Windows
+      uses: ./.github/actions/build-gstreamer
+      with:
+        config: ${{ inputs.config }}
+        cerbero-args: ${{ inputs.cerbero-args }}
+        cerbero-package-args: ${{ inputs.cerbero-package-args }}
+        cerbero-package-origin: ${{ steps.version-info.outputs.gstreamer-version }}-${{ steps.version-info.outputs.cerbero-short-sha }}
+        bootstrap-system: ${{ inputs.bootstrap-system }}
+        gst-plugins-rs-repo: ${{ inputs.gst-plugins-rs-repo }}
+        gst-plugins-rs-ref: ${{ inputs.gst-plugins-rs-ref }}
+        runtime-msi-filename: ${{ steps.version-info.outputs.runtime-msi-filename }}
+        dev-msi-filename: ${{ steps.version-info.outputs.dev-msi-filename }}
+        force: ${{ inputs.force }}
+        cleanup: ${{ inputs.cleanup }}
+        no-cache: ${{ inputs.no-cache }}
+
+    # Install either the local, pre-existing, or newly-built gstreamer MSIs
+    - id: install
+      name: Install GStreamer on Windows
+      uses: ./.github/actions/install-gstreamer
+      with:
+        runtime-installer-url: ${{ steps.check-installer-exists.outputs.runtime-url || steps.build.outputs.runtime-installer-url }}
+        runtime-installer-path: ${{ steps.build.outputs.runtime-installer-path }}
+        development-installer-url: ${{ steps.check-installer-exists.outputs.dev-url || steps.build.outputs.devel-installer-url }}
+        development-installer-path: ${{ steps.build.outputs.devel-installer-path }}
+
+    - name: Cleanup Cerbero
+      shell: msys2 -eo pipefail {0}
path: .github/actions/setup-gstreamer/action.yml
user: login: jawilson
body: Should be gated by the `cleanup` input:
```suggestion
    - name: Cleanup Cerbero
      if: ${{ always() && inputs.cleanup == 'true' && steps.get-cerbero.outcome != 'skipped' }}
      shell: msys2 -eo pipefail {0}
```
created_at: 2025-03-17T23:33:02Z
author_association: MEMBER
diff_hunk: @@ -81,6 +81,6 @@ jobs:
             ${{ vars.GSTREAMER_S3_UPLOAD_BUCKET && vars.GSTREAMER_S3_UPLOAD_PREFIX && format('s3://{0}/{1}', vars.GSTREAMER_S3_UPLOAD_BUCKET, vars.GSTREAMER_S3_UPLOAD_PREFIX) || '' }}
 
       - name: Annotate workflow run with GStreamer version
-        if: steps.build-gstreamer.outputs.gstreamer-version
+        if: steps.setupo-gstreamer.outputs.gstreamer-version
path: .github/workflows/windows.yml
user: login: btgoodwin
body: I'm amazed nothing caught that as an error/warning given the variety of other things I've read.

Fixed in 636a6dea.
created_at: 2025-03-18T12:52:57Z
author_association: NONE
diff_hunk: @@ -0,0 +1,101 @@
+name: Install GStreamer with Cerbero on Windows
+description: Installs the GStreamer MSIs either from the URLs or local paths
+inputs:
+  runtime-installer-url:
+    description: 'URL of the GStreamer runtime installer'
+    required: false
+  development-installer-url:
+    description: 'URL of the GStreamer development installer'
+    required: false
+  runtime-installer-path:
+    description: 'Local path of the GStreamer runtime installer'
+    required: false
+  development-installer-path:
+    description: 'Local path of the GStreamer development installer'
+    required: false
+outputs:
+  install-dir:
+    description: 'GStreamer install directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-install-dir }}
+  plugin-dir:
+    description: 'GStreamer plugin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-plugin-dir }}
+  bin-dir:
+    description: 'GStreamer bin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-bin-dir }}
+
+runs:
+  using: "composite"
+  steps:
+    # Mutual exclusive -- either set url or path for each.  Error if both.
+    - if: ${{ inputs.runtime-installer-url && inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both runtime-installer-url and runtime-installer-path"
+        exit 1
+    - if: ${{ inputs.development-installer-url && inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both development-installer-url and development-installer-path"
+        exit 2
path: .github/actions/install-gstreamer/action.yml
user: login: btgoodwin
body: Fixed in db2f1f37.
created_at: 2025-03-18T13:03:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,177 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              echo "runtime-url=$(aws s3 presign s3://${bucket}/${runtime_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$dev_msi" = "null" ]; then
+            echo "Checking for development installer under s3://${bucket}/${prefix}/"
+            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
path: .github/actions/setup-gstreamer/action.yml
user: login: btgoodwin
body: Fixed in 8e562417.
created_at: 2025-03-18T13:07:13Z
author_association: NONE
diff_hunk: @@ -326,33 +202,6 @@ runs:
         find ~+ -maxdepth 1 -name 'gstreamer-1.0-msvc*.msi' -exec sh -c 'echo runtime-msi=$(cygpath -aw "{}") >> $GITHUB_OUTPUT' \;
         find ~+ -maxdepth 1 -name 'gstreamer-1.0-devel*.msi' -exec sh -c 'echo dev-msi=$(cygpath -aw "{}") >> $GITHUB_OUTPUT' \;
 
path: .github/actions/build-gstreamer/action.yml
user: login: jawilson
body: We should annotate the workflow here:

```suggestion

    - name: Annotate workflow run with GStreamer version
      run: |
        echo "::notice title=Built GStreamer version ${{ steps.cerbero-config.outputs.version }}::Created installer with Cerbero short sha ${{ steps.cerbero-config.outputs.cerbero-sha }}"

```
created_at: 2025-03-24T17:33:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,127 @@
+name: Install GStreamer with Cerbero on Windows
+description: Installs the GStreamer MSIs either from the URLs or local paths
+inputs:
+  runtime-installer-url:
+    description: 'URL of the GStreamer runtime installer'
+    required: false
+  development-installer-url:
+    description: 'URL of the GStreamer development installer'
+    required: false
+  runtime-installer-path:
+    description: 'Local path of the GStreamer runtime installer'
+    required: false
+  development-installer-path:
+    description: 'Local path of the GStreamer development installer'
+    required: false
+outputs:
+  install-dir:
+    description: 'GStreamer install directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-install-dir }}
+  plugin-dir:
+    description: 'GStreamer plugin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-plugin-dir }}
+  bin-dir:
+    description: 'GStreamer bin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-bin-dir }}
+
+runs:
+  using: "composite"
+  steps:
+    # Mutual exclusive -- either set url or path for each.  Error if both or neither
+    # are set.  If path is set, verify the file exists.
+    - if: ${{ inputs.runtime-installer-url && inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both runtime-installer-url and runtime-installer-path"
+        exit 1
+    - if: ${{ inputs.development-installer-url && inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both development-installer-url and development-installer-path"
+        exit 2
+    - if: ${{ !inputs.runtime-installer-url && !inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Must specify either runtime-installer-url or runtime-installer-path"
+        exit 3
+    - if: ${{ !inputs.development-installer-url && !inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Must specify either development-installer-url or development-installer-path"
+        exit 4
+    - if: ${{ inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        if [[ ! -f "${{ inputs.runtime-installer-path }}" ]]; then
+          echo "Error: Runtime installer path does not exist: ${{ inputs.runtime-installer-path }}"
+          exit 5
+        fi
+    - if: ${{ inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        if [[ ! -f "${{ inputs.development-installer-path }}" ]]; then
+          echo "Error: Development installer path does not exist: ${{ inputs.development-installer-path }}"
+          exit 6
+        fi
+
+    # If the URL is provided, download the MSI, set path.
+    # This will error out if the file is missing.
+    - if: ${{ !inputs.runtime-installer-path }}
+      id: download-runtime
+      name: Download GStreamer Runtime MSI
+      shell: powershell
+      run: |
+        $msi_path = "$(( $pwd ))\gstreamer-runtime.msi"
+        Invoke-WebRequest -Uri "${{ inputs.runtime-installer-url }}" -OutFile "$msi_path"
+        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
path: .github/actions/install-gstreamer/action.yml
user: login: jawilson
body: Wouldn't hurt to annotate here:


```suggestion
        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
        echo "::notice title=GStreamer runtime installer downloaded::Downloaded GStreamer runtime installer from ${{ inputs.runtime-installer-url }}"
```
created_at: 2025-03-24T17:40:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,127 @@
+name: Install GStreamer with Cerbero on Windows
+description: Installs the GStreamer MSIs either from the URLs or local paths
+inputs:
+  runtime-installer-url:
+    description: 'URL of the GStreamer runtime installer'
+    required: false
+  development-installer-url:
+    description: 'URL of the GStreamer development installer'
+    required: false
+  runtime-installer-path:
+    description: 'Local path of the GStreamer runtime installer'
+    required: false
+  development-installer-path:
+    description: 'Local path of the GStreamer development installer'
+    required: false
+outputs:
+  install-dir:
+    description: 'GStreamer install directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-install-dir }}
+  plugin-dir:
+    description: 'GStreamer plugin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-plugin-dir }}
+  bin-dir:
+    description: 'GStreamer bin directory'
+    value: ${{ steps.install-gstreamer.outputs.gstreamer-bin-dir }}
+
+runs:
+  using: "composite"
+  steps:
+    # Mutual exclusive -- either set url or path for each.  Error if both or neither
+    # are set.  If path is set, verify the file exists.
+    - if: ${{ inputs.runtime-installer-url && inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both runtime-installer-url and runtime-installer-path"
+        exit 1
+    - if: ${{ inputs.development-installer-url && inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Cannot specify both development-installer-url and development-installer-path"
+        exit 2
+    - if: ${{ !inputs.runtime-installer-url && !inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Must specify either runtime-installer-url or runtime-installer-path"
+        exit 3
+    - if: ${{ !inputs.development-installer-url && !inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        echo "Error: Must specify either development-installer-url or development-installer-path"
+        exit 4
+    - if: ${{ inputs.runtime-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        if [[ ! -f "${{ inputs.runtime-installer-path }}" ]]; then
+          echo "Error: Runtime installer path does not exist: ${{ inputs.runtime-installer-path }}"
+          exit 5
+        fi
+    - if: ${{ inputs.development-installer-path }}
+      shell: msys2 -eo pipefail {0}
+      run: |
+        if [[ ! -f "${{ inputs.development-installer-path }}" ]]; then
+          echo "Error: Development installer path does not exist: ${{ inputs.development-installer-path }}"
+          exit 6
+        fi
+
+    # If the URL is provided, download the MSI, set path.
+    # This will error out if the file is missing.
+    - if: ${{ !inputs.runtime-installer-path }}
+      id: download-runtime
+      name: Download GStreamer Runtime MSI
+      shell: powershell
+      run: |
+        $msi_path = "$(( $pwd ))\gstreamer-runtime.msi"
+        Invoke-WebRequest -Uri "${{ inputs.runtime-installer-url }}" -OutFile "$msi_path"
+        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
+    - if: ${{ !inputs.development-installer-path }}
+      id: download-development
+      name: Download GStreamer Development MSI
+      shell: powershell
+      run: |
+        $msi_path = "$(( $pwd ))\gstreamer-development.msi"
+        Invoke-WebRequest -Uri "${{ inputs.development-installer-url }}" -OutFile "$msi_path"
+        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
path: .github/actions/install-gstreamer/action.yml
user: login: jawilson
body: Same as above
```suggestion
        echo "msi-path=$msi_path" | Out-File -FilePath $Env:GITHUB_OUTPUT -Append -Encoding utf8
        echo "::notice title=GStreamer development installer downloaded::Downloaded GStreamer development installer from ${{ inputs.development-installer-url }}"  
```
created_at: 2025-03-24T17:41:46Z
author_association: MEMBER
diff_hunk: @@ -37,150 +30,32 @@ inputs:
   gst-plugins-rs-ref:
     description: 'Ref/tag to use for gst-plugins-rs'
     required: false
-  force:
-    description: 'Whether to force the build'
-    required: false
-  cleanup:
-    description: 'Whether to clean the Cerbero build directory'
-    required: false
-    default: 'true'
   no-cache:
     description: 'Whether to disable restoring from cache'
     required: false
     default: 'false'
-outputs:
   gstreamer-version:
     description: 'GStreamer version'
-    value: ${{ steps.version-info.outputs.gstreamer-version }}
-  cerbero-short-sha:
-    description: 'Short SHA of the Cerbero checkout'
-    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
-  cerbero-path:
-    description: 'Path to the Cerbero checkout'
-    value: ${{ inputs.cleanup != 'true' && 'cerbero' }}
+    required: true
+    default: ''
+outputs:
path: .github/actions/build-gstreamer/action.yml
user: login: jawilson
body: Outputting the GStreamer version here is useful, as an input, it isn't used at all.

```suggestion
outputs:
  gstreamer-version:
    description: 'GStreamer version built'
    value: ${{ steps.cerbero-config.outputs.version }}
```
created_at: 2025-03-24T17:50:49Z
author_association: MEMBER
diff_hunk: @@ -81,6 +81,6 @@ jobs:
             ${{ vars.GSTREAMER_S3_UPLOAD_BUCKET && vars.GSTREAMER_S3_UPLOAD_PREFIX && format('s3://{0}/{1}', vars.GSTREAMER_S3_UPLOAD_BUCKET, vars.GSTREAMER_S3_UPLOAD_PREFIX) || '' }}
 
       - name: Annotate workflow run with GStreamer version
-        if: steps.build-gstreamer.outputs.gstreamer-version
+        if: steps.setup-gstreamer.outputs.gstreamer-version
         run: |
           echo "::notice title=Built GStreamer version ${{ steps.build-gstreamer.outputs.gstreamer-version }}::Created installer with Cerbero short sha ${{ steps.build-gstreamer.outputs.cerbero-short-sha }}"
path: .github/workflows/windows.yml
user: login: jawilson
body: This is no longer needed with my other annotation recommendations.
```suggestion
```
created_at: 2025-03-24T17:54:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,209 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+        RUNTIME_MSI_FILENAME: ${{ steps.version-info.outputs.runtime-msi-filename }}
+        DEV_MSI_FILENAME: ${{ steps.version-info.outputs.dev-msi-filename }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              aws s3 cp --no-progress "s3://${bucket}/${runtime_msi}" "cerbero/$(basename ${runtime_msi})"
+              echo "runtime-path=cerbero/$(basename ${runtime_msi})" | tee -a $GITHUB_OUTPUT
path: .github/actions/setup-gstreamer/action.yml
user: login: jawilson
body: ```suggestion
              s3_uri="s3://${bucket}/${runtime_msi}"
              aws s3 cp --no-progress "${s3_uri}" "cerbero/$(basename ${runtime_msi})"
              echo "runtime-path=cerbero/$(basename ${runtime_msi})" | tee -a $GITHUB_OUTPUT
              echo "::notice title=GStreamer Runtime Installer Downloaded From S3::Location: ${s3_uri}"
```
created_at: 2025-03-27T16:34:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,209 @@
+name: Setup GStreamer for Windows
+description: Downloads or Builds and then installs GStreamer on Windows
+inputs:
+  cerbero-repo:
+    description: 'Repository to use for Cerbero'
+    required: false
+    default: ${{ github.action_repository || github.repository }}
+  cerbero-ref:
+    description: 'Ref to use for Cerbero'
+    required: false
+    default: ${{ github.action_ref || github.ref }}
+  config:
+    description: 'Name of the configuration file to use'
+    required: false
+    default: 'win64.cbc'
+  cerbero-args:
+    description: 'Additional args to pass to Cerbero'
+    required: false
+    default: '--clocktime --timestamps -v visualstudio'
+  cerbero-package-args:
+    description: 'Additional args to pass to Cerbero package'
+    required: false
+    default: ''
+  bootstrap-system:
+    description: 'Whether to bootstrap the system'
+    required: false
+    default: 'false'
+  s3-download-paths:
+    description: 'S3 paths to download from'
+    required: false
+  s3-upload-path:
+    description: 'S3 path to upload to'
+    required: false
+  gst-plugins-rs-repo:
+    description: 'Repository to use for gst-plugins-rs'
+    required: false
+  gst-plugins-rs-ref:
+    description: 'Ref/tag to use for gst-plugins-rs'
+    required: false
+  force:
+    description: 'Whether to force the build'
+    required: false
+  cleanup:
+    description: 'Whether to clean the Cerbero build directory'
+    required: false
+    default: 'true'
+  no-cache:
+    description: 'Whether to disable restoring from cache'
+    required: false
+    default: 'false'
+outputs:
+  gstreamer-version:
+    description: 'GStreamer version'
+    value: ${{ steps.build.outputs.gstreamer-version }}
+  cerbero-short-sha:
+    description: 'Short SHA of the Cerbero checkout'
+    value: ${{ steps.version-info.outputs.cerbero-short-sha }}
+
+runs:
+  using: "composite"
+  steps:
+
+    - id: setup-msys2
+      uses: msys2/setup-msys2@v2
+      with:
+        msystem: UCRT64
+        path-type: inherit
+        release: false
+        install: >-
+          mingw-w64-ucrt-x86_64-jq
+          m4
+          mingw-w64-ucrt-x86_64-gcc-libs
+          mingw-w64-ucrt-x86_64-libwinpthread-git
+          bison
+          mingw-w64-ucrt-x86_64-diffutils
+          flex
+          mingw-w64-ucrt-x86_64-gperf
+          mingw-w64-ucrt-x86_64-make
+          mingw-w64-ucrt-x86_64-ninja
+          mingw-w64-ucrt-x86_64-perl
+
+    - shell: msys2 -eo pipefail {0}
+      run: git config --global core.autocrlf false
+
+    - id: get-cerbero
+      name: Get Cerbero
+      uses: actions/checkout@v4
+      with:
+        repository: ${{ inputs.cerbero-repo }}
+        path: cerbero
+        ref: ${{ inputs.cerbero-ref }}
+
+    - id: version-info
+      shell: msys2 -eo pipefail {0}
+      working-directory: cerbero
+      run: |
+        # Get GStreamer/cerbero version info and installer filenames
+        gstreamer_version=$(./cerbero-uninstalled packageinfo gstreamer-1.0 | awk '/Version:/ {print $2}')
+        echo "gstreamer-version=${gstreamer_version}" | tee -a $GITHUB_OUTPUT
+
+        cerbero_short_sha=$(git -C cerbero rev-parse --short HEAD)
+        echo "cerbero-short-sha=${cerbero_short_sha}" | tee -a $GITHUB_OUTPUT
+
+        echo "runtime-msi-filename=gstreamer-1.0-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+        echo "dev-msi-filename=gstreamer-1.0-devel-msvc-x86_64-${gstreamer_version}-${cerbero_short_sha}.msi" | tee -a $GITHUB_OUTPUT
+
+    - id: check-installer-exists
+      if: ${{ inputs.force != 'true' && inputs.s3-download-paths }}
+      shell: msys2 -eo pipefail {0}
+      continue-on-error: true
+      env:
+        S3_DOWNLOAD_PATHS: ${{ inputs.s3-download-paths }}
+        RUNTIME_MSI_FILENAME: ${{ steps.version-info.outputs.runtime-msi-filename }}
+        DEV_MSI_FILENAME: ${{ steps.version-info.outputs.dev-msi-filename }}
+      run: |
+        # Check installer already exists in S3
+
+        runtime_msi="null"
+        dev_msi="null"
+
+        for download_path in ${S3_DOWNLOAD_PATHS}; do
+          echo "Checking for installers under ${download_path}"
+
+          bucket=$(echo ${download_path} | cut -d/ -f3)
+          prefix=$(echo ${download_path} | cut -d/ -f4-)
+
+          if [ "$runtime_msi" = "null" ]; then
+            echo "Checking for runtime installer under s3://${bucket}/${prefix}/"
+            runtime_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${RUNTIME_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$runtime_msi" != "null" ]; then
+              aws s3 cp --no-progress "s3://${bucket}/${runtime_msi}" "cerbero/$(basename ${runtime_msi})"
+              echo "runtime-path=cerbero/$(basename ${runtime_msi})" | tee -a $GITHUB_OUTPUT
+            fi
+          fi
+
+          if [ "$dev_msi" = "null" ]; then
+            echo "Checking for development installer under s3://${bucket}/${prefix}/"
+            dev_msi=$(aws s3api list-objects-v2 --bucket "${bucket}" --prefix "${prefix}/" --query 'Contents[].Key' | jq -r 'if type!="array" then [] else . end | sort_by(length) | map(select(endswith("'"${DEV_MSI_FILENAME}"'"))) | .[0]')
+            if [ "$dev_msi" != "null" ]; then
+              aws s3 cp --no-progress "s3://${bucket}/${dev_msi}" "cerbero/$(basename ${dev_msi})"
+              echo "dev-path=cerbero/$(basename ${dev_msi})" | tee -a $GITHUB_OUTPUT
path: .github/actions/setup-gstreamer/action.yml
user: login: jawilson
body: ```suggestion
              s3_uri="s3://${bucket}/${dev_msi}"
              aws s3 cp --no-progress "${s3_uri}" "cerbero/$(basename ${dev_msi})"
              echo "dev-path=cerbero/$(basename ${dev_msi})" | tee -a $GITHUB_OUTPUT
              echo "::notice title=GStreamer Development Installer Downloaded From S3::Location: ${s3_uri}"
```
created_at: 2025-03-27T16:35:24Z
author_association: MEMBER

PR comments:
user: login: btgoodwin
created_at: 2025-03-14T13:44:18Z
body: I'm not sure what is the better play here:

1. Merge the concepts from cerbero-action to here.
2. Merge the concepts from here to cerbero-action.

(1) Is probably the least amount of work.
(2) Helps us have a place to direct users of setup-gstreamer assuming we also validate the Debian Linux side of the build, have it produce and upload tarballs.
author_association: NONE
user: login: btgoodwin
created_at: 2025-03-18T15:09:09Z
body: Sorry for the couple force commits here.  Some of these builds are actually going through which is helping to identify things that ...sure would have been nice to have been caught by a linter, like step names changing and the references not being updated.
author_association: NONE
user: login: btgoodwin
created_at: 2025-03-18T15:16:35Z
body: Aaaand once more to rebase against 1.24-lldc since it received changes this morning.
author_association: NONE
user: login: btgoodwin
created_at: 2025-03-18T17:21:38Z
body: Putting this back to draft.  Current issues:

1. It supposedly runs the upload step but doesn't upload anything.
2. The upload filenames don't contain the hash, which I thought previously they did for the sake of making some sense out of which-build-is-which.
3. If I can get the upload to work, I can finally verify the "don't build just download" logic paths.
author_association: NONE
user: login: btgoodwin
created_at: 2025-03-18T19:13:47Z
body: This _seems ready now_ @jawilson.
author_association: NONE