Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 97 additions & 16 deletions .github/workflows/native_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@ on:
publish:
description: Publish the prepared artifacts as a GitHub release
type: boolean
default: true
default: false
prepared_run_id:
description: Replay a successful preparation run without rebuilding (empty to build)
required: false
type: string
expected_source_sha:
description: Full owner source SHA from the prepared run (required for replay)
required: false
type: string
manifest_sha256:
description: Qualified manifest.json SHA256 (required for replay)
required: false
type: string
prerelease:
description: Mark the GitHub release as a prerelease
type: boolean
Expand All @@ -18,8 +30,13 @@ on:
permissions:
contents: read

concurrency:
group: native-release-${{ inputs.tag }}
cancel-in-progress: false

jobs:
build:
if: ${{ !inputs.prepared_run_id }}
name: build (${{ matrix.target }})
runs-on: ${{ matrix.runs_on }}
strategy:
Expand Down Expand Up @@ -70,6 +87,7 @@ jobs:
if-no-files-found: error

package:
if: ${{ !inputs.prepared_run_id }}
needs: build
# The SwiftPM XCFramework needs xcodebuild.
runs-on: macos-latest
Expand All @@ -91,15 +109,21 @@ jobs:
cp -R "$dir"/. "bin/${dir#artifacts/bin-}/"
done
- name: Package
run: python3 tools/package_release.py --tag "${{ inputs.tag }}" --apple-xcframework
env:
RELEASE_TAG: ${{ inputs.tag }}
run: python3 tools/package_release.py --tag "$RELEASE_TAG" --apple-xcframework
- name: Validate the XCFramework privacy manifests
env:
RELEASE_TAG: ${{ inputs.tag }}
run: >-
python3 tools/apple_privacy_manifest.py --audit-imports
"dist/stable-diffusion-native-apple-xcframework-${{ inputs.tag }}.zip"
"dist/stable-diffusion-native-apple-xcframework-$RELEASE_TAG.zip"
- name: Link the XCFramework into a SwiftPM consumer
env:
RELEASE_TAG: ${{ inputs.tag }}
run: >-
python3 tools/apple_xcframework.py consumer
"dist/stable-diffusion-native-apple-xcframework-${{ inputs.tag }}.zip"
"dist/stable-diffusion-native-apple-xcframework-$RELEASE_TAG.zip"
- name: Upload the prepared release artifacts
uses: actions/upload-artifact@v4
with:
Expand All @@ -108,24 +132,81 @@ jobs:
if-no-files-found: error
retention-days: 14

release:
verify:
needs: package
if: ${{ inputs.publish }}
if: ${{ always() && !cancelled() && (needs.package.result == 'success' || inputs.prepared_run_id != '') }}
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
actions: read
outputs:
source: ${{ steps.verify.outputs.source }}
digest: ${{ steps.verify.outputs.digest }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/download-artifact@v4
if: ${{ !inputs.prepared_run_id }}
with:
name: stable-diffusion-native-${{ inputs.tag }}
path: dist
- uses: softprops/action-gh-release@v2
- name: Verify release inputs and prepared bytes
id: verify
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
PREPARED_RUN_ID: ${{ inputs.prepared_run_id }}
EXPECTED_SOURCE_SHA: ${{ inputs.expected_source_sha }}
MANIFEST_SHA256: ${{ inputs.manifest_sha256 }}
run: |
if [ -n "$PREPARED_RUN_ID" ]; then
python3 tools/prepared_release.py replay --run-id "$PREPARED_RUN_ID" \
--tag "$RELEASE_TAG" --source "$EXPECTED_SOURCE_SHA" \
--manifest-sha256 "$MANIFEST_SHA256"
source="$EXPECTED_SOURCE_SHA"
digest="$MANIFEST_SHA256"
else
source="$GITHUB_SHA"
digest="$(sha256sum dist/manifest.json | cut -d ' ' -f 1)"
upstream="$(git ls-tree HEAD third_party/stable-diffusion.cpp | awk '{print $3}')"
python3 tools/prepared_release.py verify --tag "$RELEASE_TAG" \
--source "$source" --manifest-sha256 "$digest" --upstream "$upstream"
fi
echo "source=$source" >> "$GITHUB_OUTPUT"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
echo "Manifest SHA256: $digest" >> "$GITHUB_STEP_SUMMARY"
echo "Prepared source: $source" >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
with:
name: verified-release-${{ github.run_id }}
path: dist/
if-no-files-found: error
retention-days: 14

release:
needs: verify
if: ${{ always() && !cancelled() && needs.verify.result == 'success' && inputs.publish && github.ref == 'refs/heads/main' && github.repository == 'leehack/stable-diffusion-native' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/download-artifact@v4
with:
tag_name: ${{ inputs.tag }}
target_commitish: ${{ github.sha }}
prerelease: ${{ inputs.prerelease }}
files: |
dist/*.tar.gz
dist/*.zip
dist/manifest.json
dist/SHA256SUMS
name: verified-release-${{ github.run_id }}
path: dist
- name: Verify again and create the release without overwriting
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
EXPECTED_SOURCE_SHA: ${{ needs.verify.outputs.source }}
MANIFEST_SHA256: ${{ needs.verify.outputs.digest }}
PRERELEASE: ${{ inputs.prerelease }}
run: |
flags=()
if [ "$PRERELEASE" = true ]; then flags+=(--prerelease); fi
python3 tools/prepared_release.py publish --tag "$RELEASE_TAG" \
--source "$EXPECTED_SOURCE_SHA" --manifest-sha256 "$MANIFEST_SHA256" "${flags[@]}"
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,37 @@ XCFramework without creating a tag or GitHub release. The workflow uploads
`stable-diffusion-native-<tag>` as a run artifact retained for 14 days. Build
and packaging jobs have read-only repository permissions; only the separate
publication job has write permission, and it is skipped in preparation mode.
Normal release runs keep `publish=true` by default.
Preparation is the default (`publish=false`). To publish a previously qualified
artifact, dispatch the same workflow **on `main`**, setting `prepared_run_id`,
`expected_source_sha` (the full owner commit), `manifest_sha256` (the SHA256 of
its exact `manifest.json`), the same `tag`, and explicitly `publish=true`.
Replay skips every build and packaging job. It requires a successful
`workflow_dispatch` of this repository's release workflow on `main`, verifies
its source against current main history and the upstream gitlink, and checks
the unexpired named artifact, complete target inventory and every archive's
size/checksum. The publisher verifies the bytes again after the job transfer.
The prepared run's source, rather than the later publisher commit, becomes
the release tag. Future manifests also record that owner source in `source`;
older manifests bind it through the trusted run and upstream gitlink.

A replay with `publish=false` performs the same read-only verification. For
example, after qualification (replace the digest with its recorded value):

```bash
gh workflow run native_release.yml -R leehack/stable-diffusion-native --ref main \
-f tag=v0.3.0-2 -f prepared_run_id=41540643141 \
-f expected_source_sha=1c1f03bbb4578c3fbf80a96ad90718ba78a09407 \
-f manifest_sha256="$QUALIFIED_MANIFEST_SHA256" -f publish=false -f prerelease=true
```

The artifact retention window is 14 days; an expired or missing artifact fails
closed and needs a new preparation and qualification. Existing tags or releases
also fail closed. Same-tag runs are serialized, and publication atomically
creates a new tag before creating the release and uploading assets, with no
overwrite option. A failure after tag creation may leave a partial tag/release;
inspect it before an explicit recovery. A rerun never silently replaces it.
An explicit `publish=true` without a prepared run retains the original
build-and-publish mode.

Archive names:

Expand Down
2 changes: 1 addition & 1 deletion tests/test_apple_privacy_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def test_release_audits_the_packaged_xcframework_before_upload(self) -> None:
-1
< workflow.find("python3 tools/package_release.py")
< workflow.find(self.AUDIT)
< workflow.find("softprops/action-gh-release")
< workflow.find("tools/prepared_release.py publish")
)

def test_pull_requests_run_the_mach_o_tests_and_the_audit_on_macos(self) -> None:
Expand Down
Loading
Loading