diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index eb504d8ad..86b87e3ca 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,13 @@ name: Docker on: push: tags: ['v*'] + # Dry run. The layer cache and the build itself are only otherwise + # exercised by a real release, which is a bad place to discover that the + # cache backend is misconfigured. A dispatch builds both architectures + # exactly as a release does but PUBLISHES NOTHING: no registry push, no + # digest artifacts, no manifest list, no tag moved. See the `dry_run` + # expression on the build step's outputs. + workflow_dispatch: concurrency: # The workflow only accepts tag pushes. Include both the tag and target SHA @@ -35,6 +42,14 @@ jobs: id: ancestry run: | set -euo pipefail + # A dispatch publishes nothing, so there is no tag to vouch for and + # nothing for this gate to protect. Allow it through explicitly + # rather than by accident: the gate exists to stop an unreviewed + # commit reaching Docker Hub, and a dry run cannot. + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "allowed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi git fetch --no-tags origin main:refs/remotes/origin/main if git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then echo "allowed=true" >> "$GITHUB_OUTPUT" @@ -71,6 +86,7 @@ jobs: network=host - name: Log in to Docker Hub + if: github.event_name != 'workflow_dispatch' uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} @@ -97,7 +113,10 @@ jobs: build-args: | VERSION=${{ github.ref_name }} REVISION=${{ github.sha }} - outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true + # push=false on a dispatch. Everything above this line is identical + # between a dry run and a release, so the dry run measures the real + # build and populates the real cache. + outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'workflow_dispatch' }} cache-from: type=gha,url=http://127.0.0.1:49160/,scope=oxidex-${{ matrix.arch }},version=1 cache-to: type=gha,url=http://127.0.0.1:49160/,scope=oxidex-${{ matrix.arch }},mode=max,version=1 @@ -106,6 +125,7 @@ jobs: # job: that runs on ubuntu-latest and could not execute the arm64 image # without the emulation this design avoids. - name: Smoke test + if: github.event_name != 'workflow_dispatch' env: DIGEST: ${{ steps.build.outputs.digest }} run: | @@ -129,6 +149,7 @@ jobs: echo "Smoke test passed for $ref" - name: Export digest + if: github.event_name != 'workflow_dispatch' env: DIGEST: ${{ steps.build.outputs.digest }} run: | @@ -137,6 +158,7 @@ jobs: touch "${RUNNER_TEMP}/digests/${DIGEST#sha256:}" - name: Upload digest + if: github.event_name != 'workflow_dispatch' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: digest-${{ matrix.arch }} @@ -147,7 +169,7 @@ jobs: merge: name: Publish manifest list needs: [verify-tag-on-main, build] - if: needs.verify-tag-on-main.outputs.allowed == 'true' && needs.build.result == 'success' + if: github.event_name != 'workflow_dispatch' && needs.verify-tag-on-main.outputs.allowed == 'true' && needs.build.result == 'success' # 2x: no image build happens here -- this downloads two digest stubs and # calls `buildx imagetools create`, which is registry I/O end to end. It # is on a Warp runner rather than ubuntu-latest to avoid the GitHub-hosted diff --git a/src/core/jpeg_helpers.rs b/src/core/jpeg_helpers.rs index 3381f9c04..95609b42e 100644 --- a/src/core/jpeg_helpers.rs +++ b/src/core/jpeg_helpers.rs @@ -1716,7 +1716,12 @@ pub fn process_dji_thermal_segments(segments: &[Segment], metadata: &mut Metadat /// multiplied by positive 100, which is reproduced before calling `snprintf`. fn perl_sprintf_g(value: f64) -> String { let value = if value == 0.0 { 0.0 } else { value }; - let mut output = [0_i8; 32]; + // `libc::c_char`, not a literal `i8`: it is signed on x86_64 and UNSIGNED + // on aarch64, so hardcoding either side compiles on one architecture and + // fails on the other. This was `[0_i8; 32]`, which broke every arm64 + // build -- caught by the Docker dry run, since arm64 is only built there + // and in release.yml. + let mut output = [0 as libc::c_char; 32]; // SAFETY: `output` is writable for its full reported size, the format is a // static NUL-terminated C string with one `%g`, and `value` has the // required promoted `double` type. A default-precision rendering of any @@ -1727,7 +1732,9 @@ fn perl_sprintf_g(value: f64) -> String { let length = usize::try_from(length).unwrap_or(0).min(output.len() - 1); let bytes = &output[..length]; // `%g` emits ASCII digits, punctuation, exponent markers, or the - // implementation's ASCII inf/nan spelling. + // implementation's ASCII inf/nan spelling. `as u8` is a no-op where + // c_char is already unsigned and a reinterpret where it is signed; both + // are correct for ASCII. String::from_utf8(bytes.iter().map(|byte| *byte as u8).collect()).expect("C %g output is ASCII") } /// Emits `APP3:ImagingData`, the InfiRay IR + thermal + visible payload.