Repository navigation
Expand file tree
/
Copy patharstack-studio-linux-appimage.yml
More file actions
246 lines (230 loc) · 11.3 KB
/
Copy patharstack-studio-linux-appimage.yml
File metadata and controls
246 lines (230 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
name: ARStack Studio Linux AppImage
on:
pull_request:
paths:
- 'apps/arstack_studio/**'
- '.github/workflows/arstack-studio-linux-appimage.yml'
- 'scripts/install-linux.sh'
# When invoked by Native Preview, reuse the exact PR-head firmware
# artifact already built in that run. Do not compile a third copy of IDF.
workflow_call:
inputs:
paired_preview:
type: boolean
required: false
default: false
release_tag:
type: string
required: false
default: ''
workflow_dispatch:
inputs:
release_tag:
description: 'Build and test exact-tag Linux package; publishing is owned by the separate release workflow'
required: false
type: string
permissions:
contents: read
jobs:
appimage:
name: Build and test Linux x86_64 package
runs-on: ubuntu-22.04
timeout-minutes: 55
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
# A PR's github.sha is usually a synthetic merge commit. The paired
# firmware job uses the real PR HEAD: compile the matching GUI.
ref: ${{ (inputs.release_tag != '' && inputs.release_tag) || github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: Confirm release identity before packaging
if: ${{ inputs.release_tag != '' }}
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.release_tag }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(git rev-parse "$TAG^{commit}")"
git fetch --no-tags origin main
git merge-base --is-ancestor "$TAG^{commit}" origin/main || {
echo "Release source is not integrated into main; refusing Linux package" >&2
exit 1
}
gh release view "$TAG" --json isDraft,isPrerelease -q 'select(.isDraft==false and .isPrerelease==false)' > release-check.json
test -s release-check.json
- name: Install Linux native dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends cmake ninja-build build-essential \
libgl1-mesa-dev libxkbcommon-x11-0 libxcb-cursor0 libxcb-xinerama0 \
libxcb-xkb1 libxcb-randr0 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 \
libfuse2 unzip xvfb libdbus-1-3
- name: Install Qt 6
uses: jurplel/install-qt-action@v4
with:
version: '6.10.3'
arch: linux_gcc_64
modules: 'qtserialport'
cache: true
- name: Compile native Linux Studio and smoke test
run: |
set -euo pipefail
cmake -S apps/arstack_studio -B build-studio-linux -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DARIEC61850_WARNINGS_AS_ERRORS=ON
cmake --build build-studio-linux --target arstack_studio --parallel 2
test -x build-studio-linux/arstack_studio
QT_QPA_PLATFORM=offscreen xvfb-run -a build-studio-linux/arstack_studio --check-reference-template
QT_QPA_PLATFORM=offscreen xvfb-run -a build-studio-linux/arstack_studio --check-p0-policy
- name: Stage Linux app and matched optional release firmware
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.release_tag }}
run: |
set -euo pipefail
mkdir -p AppDir/usr/bin AppDir/usr/share/icons/hicolor/512x512/apps
cp build-studio-linux/arstack_studio AppDir/usr/bin/ARStackStudio
cp assets/studio_icon/android-chrome-512x512.png AppDir/usr/share/icons/hicolor/512x512/apps/arstack-studio.png
cat > arstack-studio.desktop <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=ARStack Studio
Exec=ARStackStudio
Icon=arstack-studio
Categories=Education;Science;Electronics;
Terminal=false
DESKTOP
cp LICENSE NOTICE COPYRIGHT.md THIRD_PARTY_NOTICES.md AppDir/usr/bin/
if [ -n "$TAG" ]; then
mkdir -p release-firmware AppDir/usr/bin/firmware AppDir/usr/bin/tools
gh release download "$TAG" --dir release-firmware --pattern 'firmware-manifest.json' --pattern '*.bin'
python3 - <<'PY'
import hashlib,json,pathlib,shutil
root=pathlib.Path('release-firmware')
manifest=json.loads((root/'firmware-manifest.json').read_text())
binfiles=list(root.glob('*.bin'))
assert len(binfiles)==1 and manifest['chip']=='esp32p4', 'Release firmware ambiguity'
src=binfiles[0]
assert hashlib.sha256(src.read_bytes()).hexdigest()==manifest['sha256'], 'Firmware hash mismatch'
output=pathlib.Path('AppDir/usr/bin/firmware')
shutil.copy2(src,output/manifest['image'])
shutil.copy2(root/'firmware-manifest.json',output/'firmware-manifest.json')
(output/'SHA256SUMS.txt').write_text(f"{manifest['sha256']} {manifest['image']}\n")
PY
fi
- name: Download matched dual-revision firmware from this exact workflow
if: ${{ inputs.paired_preview == true }}
uses: actions/download-artifact@v4
with:
name: arstack-esp32p4-smv-firmware
path: firmware-package
- name: Verify and stage pre-v3 + v3.2 preview firmware
if: ${{ inputs.paired_preview == true }}
env:
EXPECTED_SOURCE_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
test -z "${{ inputs.release_tag }}"
test -n "$EXPECTED_SOURCE_SHA"
test "$(git rev-parse HEAD)" = "$EXPECTED_SOURCE_SHA"
(cd firmware-package && sha256sum -c SHA256SUMS.txt)
python3 scripts/validate_esp32p4_revision_catalog.py firmware-package
python3 - <<'PY'
import hashlib, json, os, pathlib, shutil
root = pathlib.Path('firmware-package')
destination = pathlib.Path('AppDir/usr/bin/firmware')
destination.mkdir(parents=True, exist_ok=True)
legacy = json.loads((root/'firmware-manifest.json').read_text(encoding='utf-8'))
catalog = json.loads((root/'firmware-catalog.json').read_text(encoding='utf-8'))
expected = os.environ['EXPECTED_SOURCE_SHA']
if not (legacy['sourceCommit'] == catalog['sourceCommit'] == expected):
raise SystemExit('Linux preview firmware/GUI commits differ')
if (not legacy.get('phaseSyncPreview') or
not legacy.get('bootSourceAutostartEnabled') or
legacy.get('globalGmTraceabilityPhysicallyProven') is not False or
catalog.get('releaseChannel') != 'engineering-preview'):
raise SystemExit('Unqualified preview firmware or misleading timing evidence')
allowed = {entry['image'] for entry in catalog['images']}
allowed.update(('firmware-manifest.json', 'firmware-catalog.json', 'SHA256SUMS.txt'))
if any(p.name not in allowed for p in root.iterdir()):
raise SystemExit('Unexpected files in firmware preview artifact')
for name in sorted(allowed):
shutil.copy2(root/name, destination/name)
provenance = {
'schema': 'arstack.studio.linux.preview-provenance.v1',
'sourceCommit': expected,
'firmwareBuildId': expected[:16],
'revisionProfiles': ['pre-v3', 'v3.2'],
'firmwareReleaseChannel': 'engineering-preview',
'siliconV3PhysicallyQualified': False,
'globalGmTraceabilityPhysicallyProven': False,
'stableRelease': False,
}
(pathlib.Path('AppDir/usr/bin')/'PREVIEW-PROVENANCE.json').write_text(
json.dumps(provenance, indent=2, sort_keys=True)+'\n', encoding='utf-8')
print('Linux paired preview: exact source + both revision images + provenance PASS')
PY
# Both immutable stable-tag and paired-preview paths share one verified
# flasher source. The GUI-only PR AppImage never claims firmware support.
- name: Bundle verified Linux espflash v4.5.0
if: ${{ inputs.release_tag != '' || inputs.paired_preview == true }}
run: |
set -euo pipefail
mkdir -p AppDir/usr/bin/tools
curl -fLsS https://github.com/esp-rs/espflash/releases/download/v4.5.0/espflash-x86_64-unknown-linux-gnu.zip -o espflash.zip
echo 'dcd7fd822f247df18966935bbb10c7cb4b700c87172349633e9e323ee2542717 espflash.zip' | sha256sum -c -
unzip -j espflash.zip 'espflash' -d AppDir/usr/bin/tools
chmod +x AppDir/usr/bin/tools/espflash
AppDir/usr/bin/tools/espflash --version
- name: Check matched Linux preview with the production Studio
if: ${{ inputs.paired_preview == true }}
env:
ARSTACK_STUDIO_FIRMWARE_DIR: ${{ github.workspace }}/AppDir/usr/bin/firmware
ARSTACK_STUDIO_ESPFLASH: ${{ github.workspace }}/AppDir/usr/bin/tools/espflash
run: |
set -euo pipefail
QT_QPA_PLATFORM=offscreen xvfb-run -a build-studio-linux/arstack_studio --check-firmware-contract
- name: Bundle Qt and create single-file AppImage
uses: miurahr/install-linuxdeploy-action@v1.8.0
id: linuxdeploy
with:
plugins: qt appimage
- name: Generate AppImage and release checksum
env:
QML_SOURCES_PATHS: ${{ github.workspace }}/apps/arstack_studio/qml
VERSION: ${{ inputs.release_tag || 'preview' }}
run: |
set -euo pipefail
export QMAKE="$(command -v qmake6 || command -v qmake)"
${{ steps.linuxdeploy.outputs.linuxdeploy }} \
--appdir AppDir \
--executable AppDir/usr/bin/ARStackStudio \
--desktop-file arstack-studio.desktop \
--icon-file AppDir/usr/share/icons/hicolor/512x512/apps/arstack-studio.png \
--plugin qt --output appimage
shopt -s nullglob
images=( ./*.AppImage )
test "${#images[@]}" -eq 1
mv "${images[0]}" ARStack-Studio-linux-x86_64.AppImage
chmod +x ARStack-Studio-linux-x86_64.AppImage
APPIMAGE_EXTRACT_AND_RUN=1 QT_QPA_PLATFORM=offscreen \
xvfb-run -a ./ARStack-Studio-linux-x86_64.AppImage --check-reference-template
APPIMAGE_EXTRACT_AND_RUN=1 QT_QPA_PLATFORM=offscreen \
xvfb-run -a ./ARStack-Studio-linux-x86_64.AppImage --check-p0-policy
if [ -n "${{ inputs.release_tag }}" ] || [ "${{ inputs.paired_preview }}" = "true" ]; then
# Test real extracted AppImage paths, not a developer-env override.
env -u ARSTACK_STUDIO_FIRMWARE_DIR -u ARSTACK_STUDIO_ESPFLASH \
APPIMAGE_EXTRACT_AND_RUN=1 QT_QPA_PLATFORM=offscreen \
xvfb-run -a ./ARStack-Studio-linux-x86_64.AppImage --check-firmware-contract
fi
sha256sum ARStack-Studio-linux-x86_64.AppImage > ARStack-Studio-linux-x86_64.sha256
- name: Upload reviewed Linux artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.paired_preview && 'arstack-studio-linux-esp32p4-dual-preview' || 'arstack-studio-linux-x86_64' }}
path: |
ARStack-Studio-linux-x86_64.AppImage
ARStack-Studio-linux-x86_64.sha256
if-no-files-found: error
retention-days: 14